Как начать писать код уже сейчас, а не тратить 10 тысяч лет на настройку Вебпака
В прошлом уроке мы попробовали написать совсем крошечное приложение из трёх компонентов, но уже поняли, что с createElement
это чертовски сложно — читаемость кода почти нулевая, конечно.
Тем не менее, давайте освежим память прежде чем пойдём в инструменты, которые скроют от нас детали реализаций:
- Реакт — это чистый Джаваскрипт и работает на функции
React.createElement
; React
это не рендерер, а библиотека для построения дерева элементов и его обновления, за это отвечает ВиртуалДОМ;- За рендер в браузер отвечает библиотека
ReactDOM
, но бывают ещёreact-tv
,react-native
и другие; - Реакт это компонентный подход, в котором для передачи данных используют пропы — атрибуты с любыми типами данных (вплоть до функций).
Идем дальше. Учтите, что в этом уроке мы будем настраивать вашу систему для работы и это не должно вас пугать. Погнали!
С приветствия я вам рекомендовал пройти урок про инструменты и сейчас вам это должно помочь: мы будем работать с терминалом.
Для начала нам нужно поставить Ноду (Node.js): она выполняет джс-файлы не в браузере, а в виртуальном сервере.
Скачайте установщик с сайта nodejs.org и через него установите.
Поставьте из своих репозиториев: apt-get
, yum
и так далее.
В Маке нет встроенного пакетного менеджера (а зря!), поэтому сначала нужно поставить Homebrew.
После этого вы можете ставить пакеты и они будут очень аккуратно вписаны в систему.
Поставьте Ноду через команду brew install node
.
В Ноде есть пакетный менеджер npm. Он работал плохо и медленно, поэтому ребята из Фейсбука сделали свой пакетный менеджер Yarn: он работает быстрее и предсказуемее, поэтому ставьте его. Установка описана на его сайте, учитесь добывать информацию сами из документаций.
Что такое вообще пакетный менеджер и почему предсказуемость и скорость так важны?
-
Во-первых, во время разработки вы будете использовать много сторонних модулей — ведь кучу вещей кто-то за вас уже когда-то написал, осталось это только подключить и использовать.
-
Во-вторых, говоря про скорость: у каждого пакета могут быть свои зависимости (по 2-3), а в проекте, например, 100 зависимостей. Вместо 100 у вас устанавливается 300: 100 основных пакетов и их зависимостей. Может быть долго!
-
В-третьих, пакеты соблюдают Семвер — семантичное версионирование. Дело в том, что пакеты обновляются, а ломать работающие приложения из-за этого не хочется, поэтому у нас есть версионирование
MAJOR.MINOR.PATCH
(например, Реакт сейчас — 16.2.0).npm
раньше не уважал прописанные вpackage.json
(основной файл проекта) версии и порою всё ломалось. В Ярне такого нет изначально.
У Ярна нет своей базы пакетов, он использует базу нпм.
Окей, Ярн поставили, Нода тоже стоит, всё замечательно. Тут я делаю уточнение: пакеты, которые ставятся через Ярн (ну или нпм, но, опять же, нпм лучше не использовать) могут быть локальными и глобальными. Ставятся они по-разному:
# поставить в систему
yarn global add create-react-app
# поставить локально в проект и записать в `package.json`
yarn add date-fns
# поставить локально и записать в package.devDependencies
yarn add --dev webpack
Пакеты проекта в джс-мире описываются в файле package.json
в секциях dependencies
и devDependencies
.
Все зависимости, которые используются в приложении: Реакт, normalize.css из курса по вёрстке, react-day-picker для работы с датами и другие.
Зависимости, которые помогают работать приложению: Еслинт, Преттир, Вебпак и другие.
Чтобы установить все пакеты проекта из package.json
, есть команда yarn
(альяс к yarn install
).
Настройку закончили, переходим к практике.
react-scripts (да, ссылка на нпм, но со всеми нпм-пакетами работаем через Ярн) это пакет, который содержит в себе готовые настройки для Вебпака, Еслинта и Бейбеля — о них мы поговорим в будущем.
Отличие react-scripts
от бойлерплейтов в том, что у вас нет кучи конфигов в проекте, они все скрыты от вас в глубинах node_modules/react-scripts/...
.
Чтобы воспользоваться мощью r-s, достаточно поставить как зависимость и прописать два скрипта в package.json
:
...
{
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build"
}
}
Но вместо этого давайте воспользуемся официальным способом: create-react-app
.
CRA это утилита, которая создаёт проект с react-scripts
и несколькими компонентами для демонстрации. CRA ставится глобально в систему через Ярн либо можно воспользоваться командой yarn create ...
:
# не будет ставить в систему,
# а временно скачает и после удалит
# [dir] это ваша будущая директория, пишется без скобок
yarn create react-app [dir]
Кстати,
yarn create ...
ищет пакет с названиемcreate-*
и вызывает его, поэтому вы можете в будущем воспользоватьсяyarn create react-native-app
для создания проекта черезcreate-react-native-app
Создайте через CRA проект aviasales-demo-frontend
Давайте взглянем на структуру директорий:
/private/tmp $ ls -al aviasales-demo-frontend
total 752
e Mar 3 22:38 .
e Mar 3 22:37 ..
e Mar 3 22:38 .gitignore
e Mar 3 22:38 README.md
e Mar 3 22:38 node_modules
e Mar 3 22:38 package.json
e Mar 3 22:38 public
e Mar 3 22:38 src
e Mar 3 22:38 yarn.lock
.gitignore
если вы проходили Гитхауту из урока инструментов, вы знаете зачем нужен этот файл. Если нет — идите читать. CRA создал этот файл с директориями и файлами, которые нужно кинуть в игнор в Гите;node_modules
— все модули проекта. Туда лезть никогда нельзя;package.json
— главный конфиг проекта, от него работают Ярн и нпм;src
— директория, где вы пишете код;public
— лучше всего ответит документация react-scripts, Using thepublic
Folder;yarn.lock
— файл Ярна, где он хранит информацию обо всех установленных пакетах проекта.build
— этой директории пока нет, но мы с ней разберёмся на третьем этапе.
Я горячо рекомендую сначала прочитать документацию
react-scripts
: даже если вы не поймёте, это пассивные знания, которые могут всплыть когда вам они понадобятся, в противном случае вы будете бегать с горящей жопой и не знать что искать когда возникнет проблема. Как вы понимаете, это правило универсально для всех инструментов, которые вы будете использовать. Знайте свой инструмент.
Официальный Юзер Гайд очень полный, я подчеркну несколько моментов:
- Displaying Lint Output in the Editor ,
- Installing a Dependency,
- Importing a Component,
- Adding a Stylesheet (CSS),
- Adding Images, Fonts, and Files,
- Using the
public
Folder.
Можно ли использовать react-scripts
в своём проекте не создавая проект через create-react-app
? Можно, но не забудьте о том, что react-scripts
ориентируется на определенную структуру.
Окей, с этим разобрались. Запускаем проект через команду yarn start
и начинаем кодить.
Запускаем браузер по указанному адресу и видим наш сайт:
Открывайте в Вскоде src/App.js
и узрите тот самый Джсх, который наконец-то работает!
Поставьте три плагина: mgmcdermott.vscode-language-babel
, dbaeumer.vscode-eslint
и esbenp.prettier-vscode
.
Подсветка синтаксиса: он работает с ES2015+ (стандарт Джаваскрипта), JSX и другими вещами. Вообще, babel
это парсер Джаваскрипта и компилятор в понятный браузерам: например, вы можете использовать ES2018 сегодня, а Бейбел переведет их в ES5 (который поддерживают все браузеры). Почему пакет называется language-babel
? Потому что babel
это в первую очередь парсер и этот пакет его использует.
Плагин к ESLint. Еслинт — удобная утилита для проверки качества вашего кода. У него есть плагины и готовые конфиги, в create-react-app
ставится свой, достаточно мягкий. В будущем мы будем использовать стайлгайд от Эйрбнб — вот он лютый.
Преттир это форматер кода. Больше никаких споров про отступы, переносы, запятые с точками и прочего.
Эти настройки идут в дополнение к вашим, а не полностью их заменяют. Разбирать их не буду, из значений, думаю, понятно что к чему относится, но если нет — не забудьте в чате прояснить.
{
"editor.tabSize": 2,
"editor.formatOnSave": true,
"editor.formatOnPaste": true,
"prettier.eslintIntegration": true,
"javascript.format.enable": false,
"javascript.validate.enable": false,
"files.associations": {
"*.jsx": "javascriptreact",
"*.js": "javascriptreact"
},
"emmet.syntaxProfiles": {
"javascript": "jsx"
}
}
После того, как создадите приложение через create-react-app
, вам нужно будет связать еслинт из него с плагином в Вскоде. Как это сделать? Ответ в официальной документации.
Окей, Вскод настроили, всё заработало, теперь нужно разобраться что за код в src/App.js
: импорты, экспорты и классы это нечто новое.
import React, { Component } from "react";
import logo from "./logo.svg";
import "./App.css";
class App extends Component {
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to React</h1>
</header>
<p className="App-intro">
To get started, edit <code>src/App.js</code> and save to reload.
</p>
</div>
);
}
}
export default App;
В 2015 году в Джсе появилась система импортов и экспортов. Если вкратце, то один файл может экспортнуть выражение, а второй — импортнуть. Это называется модулями.
В Джс экспорты бывают дефолтные и именованные.
// sum.js
export default function(left, right) {
return left + right;
}
// app.js
import sum from "./sum.js";
sum(1, 4); // 5
На самом деле, мы можем называть наш импорт как угодно:
import fuckThis from "./sum.js";
fuckThis(1, 4); // 5
Но как быть, если вы хотите экспортнуть несколько констант или функций из одного файла? Для этого есть именованные экспорты.
// math.js
export function sum(left, right) {
return left + right;
}
export const divide = (left, right) => {
return left / right;
};
export const mainNumber = 42;
// app.js
import { mainNumber, sum, divide } from "./math.js";
divide(mainNumber, sum(2, 4)); // 7
Джс сначала выполнит sum(2, 4)
, который вернёт 6
, затем это число подставится как второй аргумент divide
.
Здесь у нас нет роскоши именовать как угодно, но мы можем импортить as
:
// app.js
import { mainNumber as myLovingNumber } from './math.js';
Вебпак — это бандлер модулей. В какой-то момент создатель Вебпака подумал: а зачем нам копировать файлы, подключать всё в index.html
по-старинке, если мы можем в джс-файлах импортить вообще всё что угодно?
Для этого используются лоадеры. Вам пока об этом думать не нужно, но вкратце объясню, что лоадеры отвечают за то, что вернёт импорт.
Например, если мы импортим ЦСС-файл, то сработают лоадеры style-loader
и css-loader
и Вебпак вставит ЦСС-код в <style></style>
теги в начале страницы.
Если мы импортим шрифты, картинки или ещё что, сработает file-loader
, который при импорте вернёт сгенерированный путь к файлу.
import logo from "./logo.svg";
console.log(logo); // "/static/images/0dcbbaa7013869e351f.png"
Кстати, при импорте директории Вебпак по-умолчанию лезет за index.js
или index.jsx
файлом, поэтому не нужно писать import Header from './Header/index.js
, да .js
можно было бы опустить:
import Header from "./Header";
Окей, с импортами и экспортами разобрались, теперь нас интресует второй новый синтаксис:
class X extends Component {
render() {}
}
Это классы, а если конкретнее — классовый компонент Реакта. Мы с этим ещё разберёмся в будущем, сейчас лишь хочу сказать что классовый компонент конкретно здесь идентичен функциональному: метод render()
в классе тоже должен вернуть дерево элементов.
Собственно, Джсх и отвечает за дерево элементов. Про Джсх мы говорили в четвертом уроке, давайте для примера переведём наше приложения про фильмы на Джсх и избавимся от React.createElement
.
// создаём компонент Img, который возвращает <img />
function Img(props) {
return React.createElement("img", {
className: props.className,
src: props.src,
alt: props.alt
});
}
// создаём компонент Movie, который возвращает
// |---------| 2001: A Space Odyssey
// |---------|
// |---------|
// |---------|
// |---------|
// |_________|
function Movie(props) {
return React.createElement(
"div",
{ className: "movie-page" },
// чилдреном может быть массив
[
// создаём элемент из компонента Img
React.createElement(Img, {
className: "movie-img",
src: props.imgSrc,
alt: props.title
}),
React.createElement("h1", { className: "movie-title" }, props.title)
]
);
}
// создаём
const App = React.createElement(Movie, {
title: "2001: A Space Odyssey",
imgSrc: "https://i.imgur.com/vaZoNCA.jpg"
});
ReactDOM.render(App, document.getElementById("app"));
// src/App.js
function Img(props) {
return <img src={props.src} className={props.className} alt={props.alt} />;
}
function Movie(props) {
return (
<div className="movie-page">
<Img className="movie-img" src={props.imgSrc} alt={props.title} />
<h1 className="movie-title">{props.title}</h1>
</div>
);
}
export default Movie;
Аккуратнее стало? Намного!
Почему мы избавились от ReactDOM.render()
? Потому что этим занимается src/index.js
.
В чём отличие index.js
от App.js
? App.js
это главный файл реакт-приложения, а index.js
— фронтэнд-приложения: в вашем проекте может быть много других сторонних библиотек, с которыми нужно будет работать.
Урок получился большой и даже больше про настройку, но в конце мы получили проект, с которым можно работать и для этого нам не нужно заниматься настройкой Вебпака.
Помните, что create-react-app
и конкретно react-scripts
это ваше спасение и он подходит даже для крупных проектов, поэтому стесняться его не надо.