Введение в React и Redux для бекенд-разработчиков
Если вы как я долгое время считали, что JavaScript — это такой «игрушечный» язык на котором пишут анимашки для менюшек и падающий снежинки на форумах под новый год, а потом очнулись в 2016 году с мыслями WTF: react, flux redux, webpack, babel, … не отчаивайтесь. Вы не одиноки. Материалов по современному фронтенду в сети много, даже слишком много. Под катом еще одно альтернативное мнение о том, каково это учить JavaScript в 2016 году.
Итак, нам потребуются: React, React Dev Tools, React-Hot-Loader, React-Router, Thunk, Redux, Redux Dev Tools, Semantic-UI, Webpack, Babel и npm.
На первый взгляд много. Сравним с бекендом: MVC-фреймворк, ORM, Data Mapper, IOC-контейнер, логер, профайлер, очереди, управление конфигурациями, сборка и выкладка… Список можно продолжить, но думаю идея понятна и так: с ростом сложности решаемых задач растет и сложность инструментов. Все чаще мы употребляем термин Web App вместо Web Site, акцентируя внимание на богатых возможностях современных веб-приложений.
Почему именно этот стек?
Если вдуматься и отбросить все лишнее, то единственный ресурс постиндустриальной эпохи, через который можно выразить все остальные — это время. Освоение каждой новой технологии требует затрат времени, а значит более перспективны инвестиции в технологии, которые не устареют в ближайшие пару месяцев. Дополнительный плюс получают технологии с пологой кривой обучения.
React + Redux VS Angular VS Yet Another JS Framework
Только ленивый не сравнил Angular с React«ом (приписав при этом, дескать, сравнение не корректно, Angular — фреймворк, React — библиотека). Пойдем от обратного. Почему бы не выбрать что-нибудь эдакое, типа Vue, Ember или, упаси боже, Elm?
- Поддержка крупных вендоров
- Размер сообщества
Благодаря этим факторам, вероятность выживания Angular и React выше. Простите, другие замечательные хипстерские решения, нам не по пути. Итак, почему React? Лично для меня выбор был не простым:
- Меня пугали jsx-файлы
- Я кое-что умел на Angular 1.x и переходить на другую технологию было психологически не комфортно
- ng2 по-умолчанию идет в комплекте с TypeScript, что мне как стороннику статической типизации ближе
- ng подкупал подходом «работает из коробки». Копаться в многообразии npm-пакетов решительно не хотелось.
Короче, я заставил себя изучить все статьи-сравнилки и написать Todo App на React, что склонило чашу весов в противоположную сторону. Ключевыми факторами для меня стали:
- HTML-шаблоны Angular — ужасны и их синтаксис меняется от версии к версии. В React шаблон — это JavaScript, потому что компонент — не более чем View. Сообщения об ошибках в React лучше.
- Как ни странно, TypeScript. При более детальном изучении оказалось, что не все так прекрасно. Во-первых TypeScript — это не полноценный язык со статической типизацией, а транспилер. Это сильно ограничивает возможности использования шаблонов и мета-программирования. Во-вторых, далеко не все npm-пакеты идут в комплекте с d.ts-файлами. Короче, Flow показался проще в прикручивании. В-третьих, у TypeScript есть как ярые фанаты, так и противники. Если фанаты TS сравнительно лояльны к ES6, то обратное — не верно. ES6 получает дополнительное очко к Bus Factor.
Если вам нравится TypeScript, ничто не мешает использовать его вместе с React. Просто конкретно мне он пока не дал критического объема преимуществ, чтобы заставить тратить время на еще один элемент в стеке.
- Доклад Дэна Абрамова про «путешествия во времени». Если ваш опыт в бекенде похож на мой, то вы без труда увидите, что новомодный flux — это CQRS и Event Sourcing «вид в профиль». Вместо проекций — редюсеры, а вместо команд и доменных событий — экшны. А если вы работали, например, с WPF, то разобраться с React — вообще дело пары вечеров.
Да, Redux можно использовать и с Angular, он никак не привязан к React, но для React уже есть react-redux и react-hot-loader. Наверное, для Angular тоже есть, но мейнтейнер Redux«а явно на стороне React.
Для React и Redux доступно два расширения Chrome. Рекомендую поставить оба, чтобы сделать отладку приятной.
Таким образом, связка React + Redux:
- Более-менее проста в изучении, потому что в основе лежит простая идея
ui = f(state => props)
, где f — это реакт-компонент, state — redux, аstate => props
— это react-redux. - Не тащит за собой дополнительных зависимостей
- Обладает лучшим на данный момент Tool Support (IDE и плагины для Chrome)
Есть еще всякие ништяки, вроде React Native, но я им не пользовался, поэтому поделиться на эту тему мне, к сожалению, нечем.
А flux и все эти модные словечки. Как это работает?
Возможно, для фронтенда flux — это некое откровение. Для бекендщика разница между CQRS и flux — не велика. React — это наше представление. Оно может зависеть от props (read-only) или state (mutable). В зависимости от state и props компонент может отображаться по-разному. Эта логика содержится в методе
render
. Компонент сам себя не перерисовывает. Вместо этого, он полагается на экосистему React. Мы можем либо изменить свое состояние (с помощью метода setState
), либо быть перерисованными извне (переданы новые props). Обработчики событий для UI-элементов передаются через props. Получается такой код
.Состояние приложение хранится в Redux и представляет собой json-объект. Redux следит за изменение объекта. Изменением считается изменение ссылки, поэтому вместо изменения текущего состояния необходимо конструировать новое путем копирования и модификации старого. Проще всего это сделать через spread-оператор:
const newState = {...prevState, somethingNew: 'blah-blah'}
Пакет react-redux осуществляет односторонний байндинг
redux state => react component
с помощью метода connect. При изменении состояния Redux сам перерисует необходимые компоненты, передав в props dispatch и часть общего state, хранимого в Redux. Какую часть состояния и какие функции на основании store.dispatch
передавать — решать вам. Я рекомендую передать все обработчики событий компонента и не «светить» dispatch в компонентах.State содержится в Redux, но и у компонентов есть свой state. Какой из них использовать?
Разработчик Redux предлагает делать как удобнее. Это не совсем формальный совет. У нас сложилась практика использовать state компонента только для форм или в целях оптимизации.
JSX
JSX — это не JavaScript. Да, React можно писать без JSX, но проще тогда без React«а. Вообще ситуация с HTML-шаблонами напоминает мне засилье шаблонизаторов для PHP лет десять назад. Самым монструозным из всех был конечно Smarty. Мне казалось, что люди сошли с ума. Как иначе можно было объяснить желание написать шаблонизатор… для шаблонизатора?
JSX — простой и понятный способ использовать JavaScript в шаблонах. Вам не нужно учить дополнительный ЯП, просто оберните теги в (скобки)
, а код внутри тегов {в другие скобки}
. И все. Да, так просто. Если вас беспокоит разделение логики и представления, перестаньте беспокоиться прямо сейчас. React — это View-библиотека. За состояние приложения (в т.ч. поведение) отвечает Redux. React может «диспатчить» сообщения в store, а Redux будет их обрабатывать либо через редьюсеры (чистые функции), либо через специальные middleware (побочные эффекты).
React позволяет создавать functional stateless-компоненты:
const StatelessComponent = props => (Hello, {props.name}!)
Или компоненты-классы:
class Hello extends React.Component {
render() {
return Hello, {this.props.name}
;
}
}
Функциональная запись короче и лучше читается. Но я рекомендую не увлекаться экономией строк и вместо:
export default props => ({props.title})
использовать чуть менее лаконичное, но более безопасное:
const StatelessComponent = props => ({props.title})
StatelessComponent.propTypes = {…}
export default StatelessComponent
Во-первых, если вы не передадите параметры, то React недвусмысленно намекнет в консоли, что вы не правы. Во-вторых, WebStorm умеет анализировать
PropTypes
и при авто-дополнении заботливо вставит все required props.Babel
Если вы не поняли на каком языке примеры кода выше, не расстраивайтесь. Это не JavaScript, ну не совсем JavaScript. Это ES6 + JSX. С JSX мы разобрались в параграфе выше — это просто синтаксический сахар для шаблонизации (почти как
=$var?>
в PHP или @Model.Param
в Razor). С ES6 ситуация чуть сложнее и запутаннее. Если коротко:
- JavaScript собрали на коленке под нужны тогдашнего интернета, который представлял собой действительно в основном гипер-текст.
- Прошло некоторое время и в сайты начали пихать все что угодно, кроме текста.
- Язык в существующем виде перестал удовлетворять нуждам рынка. Консорциум ECMA стал придумывать всякие новые фичи и стандарты языка, да с такой скоростью, что браузеры не успели все внедрить.
- В сообществе психанули и написали Babel — транспилер из JavaScript в… JavaScript. Ну в смысле из «нового» JS в «старый», который браузеры поддерживают.
Babel умеет транспилить не только JS, но и JSX, что позволяет писать React-приложения на ES6.
Да, есть React.createElement
. Можно писать на React-приложения и на ES5, но зачем?
Стоит отметить, что ES6 — это не истина в последней инстанции. Некоторые фичи до сих пор являются экспериментальными (например, генераторы) и для их использования потребуются полифиллы (библиотеки, реализующие экспериментальные фичи стандарта). Частично из-за этого мы решили отказаться от redux-saga в пользу redux-thunk, хотя и идея диспатчить функции до сих пор не кажется мне изящной (она просто работает).
Webpack
Так, то есть пишем мы на ES6 + JSX, а в бразуере выполняется минифицированный JS. Все это напоминает историю изобретения высокоуровневых ЯП. Люди могли писать более эффективные программы на ассемблере, но предпочли удобство и продуктивность. Раз есть исходники и компилятор (транспилятор в нашем случае), то потребуется и система сборки. Если в вашей пещере было достаточно тепло и уютно, возможно, названия grunt и gulp вам ничего не говорят. Что к лучшему. На данный момент, можно считать (слава богу), что для JS есть только один сборщик — Webpack — оставивший конкурентов позади. Можно считать, что webpack — аналог maven или msbuild (кому что ближе) в мире фронтэнда. Не смотря на то что, конфиги webpack«а на первый взгляд напоминают некромантские свитки, через какое-то время привыкаешь. Наверное, каждый любитель фронтенда должен хотя-бы раз в своей жизни написать tutorial по настройке webpack, также как каждый фанат ФП — tutorial по монадам.
Что нужно знать про webpack:
- Вам потребуется кто-то, кто умеет его настраивать
- npm start — для запуска dev-сервера
- npm run build для сборки фронта на продакшн
Вообще Webpack собирает не только JS, но еще и sass, svg, шрифты и вообще все что душе угодно, но я пока еще не готов написать полноценный туториал, так что поищите на просторах интернета.
Npm
У Ruby есть gem«ы, у php — composer, у .NET — nuget. Короче, JavaScript тоже потребовался пакетный менеджер. Изначально npm использовался в nodejs-разработке (отсюда и название — node package manager), а для фронта использовался bower. Потребность в последнем как-то отпала сама собой с повсеместным переходом использованием ES6, Webpack и TypeScript. Этот параграф добавлен лишь для того чтобы отметить, что npm использует файл package.json, внутри которого можно написать:
"scripts": {
"build": "webpack --config webpack/config.js -p",
"start": "webpack-dev-server --config webpack/config.js"
}
Без этих строк
npm run build
и npm start
не заработают. Было бы логичнее вызывать npm build
, а не npm run build
, но эта команда зарезервирована для внутренних целей npm, так что ничего не выйдет.
React-Hot-Loader
Ключ
--hot
запускает dev-сервер webpack«а с «горячей заменой». Согласитесь, билдить при каждом изменении — довольно уныло. HMR (hot moudle replacement) делает это за вас. react-hot-loader позволяет избежать при этом перезагрузки страницы и потери текущего состояния. Настройка hot-loader«а правда, довольно тонкая работа и вообще фича — экспериментальная и работает не всегда. Особенно сложные отношения у горячей замены с react-router. Но к хорошему быстро привыкаешь и рано или поздно вам захочется написать if(module.hot)
для того, чтобы страница не перезагружалась. React-Router и Thunk
Основная ниша React«а в Web — это конечно SPA-сайты. А какой SPA-сайт без навигации и общения с сервером. Первую задачу решает react-router. Здесь альтернатив нет. Из неприятных сюрпризов:
- Четвертая версия не совместима с третьей из-за чего у нее проблемы с пакетом history.
- Для hot-reload нужно соблюдать некоторые нюансы, иначе в консоли будут появляется предупреждения о том, что нельзя заменить route. К счастью ошибки достаточно информативные и исправить их просто.
RouterMiddleware
может сломать Redux плагин при совместном использовании.- Не очевидно, что вместо тегов
можно использовать JavaScript-объекты и передать их в компонент роутера
. Это бывает полезно, при разработке модульных приложений, когда структура маршрутов не известна заранее.
Thunk — это middleware для redux, позволяющее диспатчить функции вместо объектов. Чаще всего используется для запросов к серверу. В компоненте делать запросы к серверу — не комильфо. Редьюсеры — вообще дожны быть чистыми функциями. Ничего не остается, как делегировать это middleware.
Альтернатива thunk — redux-saga. Сага предоставляет больше возможностей, но у нее довольно крутая кривая обучения, и она тащит за собой полифиллы для генераторов. При правильном проектировании логики в компонентах нет, а connect к стейту Redux в основном производится через фабричный метод. В общем, мне показалось, что нет большой разницы, как именно управление перейдет к fetch
и будет ли написано yield
или then
. На сайте Redux пример с thunk, так что по совокупности причин сагу я отложил до лучших времен.
Semantic-UI
Раз мы заговорили про SPA-приложения, то кроме навигации и запросов к серверу нужны еще компоненты, которые будут ту самую серверную информацию отображать. Для React есть обвязки Bootstrap, Material UI, Syncfusion Web Essentials (хотя эти обвязки не честные — там внутри jQuery). Наш выбор остановился на Sematic-UI. Решение удалось принять очень быстро — сначала отмели платные компоненты. Material UI не стали использовать из-за обилия анимации (сложнее модифицировать). Остались Bootstrap и Semantic. На Бутстрапе уже пол интернета сделано и в целом, Семантик показался более визуально-привлекательным. В общем, остановились на нем. Сразу оговорюсь, что использование Semantic UI — строго опционально, потому что минифицированная версия весит около 500 кб.
Так что разрабатывать фронтенд в 2016 году вполне себе комфортно. Да инструментов много, многие библиотеки не совместимы, новые версии выходят очень часто. Это разумная плата за гигантский скачок в качестве фронтенд-стека.
Комментарии (19)
11 апреля 2017 в 03:56
–11↑
↓
>node > 0.1*0.1 0.010000000000000002 > 0.1*0.2 0.020000000000000004 > 0.1*0.3 0.03 > 0.1*0.4 0.04000000000000001 > 0.1*0.5 0.05 > 0.1*0.6 0.06 > 0.1*0.7 0.06999999999999999 > 0.1*0.8 0.08000000000000002 > 0.1*0.9 0.09000000000000001
У меня все.11 апреля 2017 в 04:02
+11↑
↓
IEEE 754
У меня всё.
11 апреля 2017 в 04:09
+2↑
↓
Что все? Это IEEE 754, во всех языках, которые его используют так будет.А статья — очередное «вступление в мир моды на быдлокод в JS», которых, как сам автор сказал, уже очень много и потому не нужна
11 апреля 2017 в 04:22
+2↑
↓
golang:package main import "fmt" func main() { var a float32 = 0.1 var b float32 = 0.1 fmt.Println(a * b) b = 0.9 fmt.Println(a * b) }
> 0.010000001 > 0.089999996
11 апреля 2017 в 06:46
+2↑
↓
Erlang/OTP 19 [erts-8.1] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] 1> 0.1 * 0.1. 0.010000000000000002 2> 0.1 * 0.2. 0.020000000000000004 3> 0.1 * 0.3. 0.03 4> 0.1 * 0.9. 0.09000000000000001
11 апреля 2017 в 08:21 (комментарий был изменён)
0↑
↓
D:
import std.stdio; void main() { writeln( 0.1 + 0.2 ); // 0.3 writeln( 0.1 + 0.2 == 0.3 ); // true }
11 апреля 2017 в 08:31
0↑
↓
> Первую задачу решает react-router. Здесь альтернатив нет
react-router-redux?11 апреля 2017 в 08:49
0↑
↓
Разве эта библиотека — не построена поверх react-router? Мне казалось, что она просто дополнительно пропускает переходы через store, чтобы сохранить time travel. Нет?
11 апреля 2017 в 08:39
0↑
↓
Как короткое описание своего технологического стека c возможностью изучать тему «вглубь» по ссылкам — статья понравилась.
Немного критики. К сожалению, ряд моментов режет глаз.Сравним с бекендом: MVC-фреймворк, ORM, Data Mapper, IOC-контейнер…
Почему MVC? Почему фреймворк? Почему ORM? И т.д. Это вовсе не необходимые компоненты бэкенда. Я бы назвал их «модными», но бэкенд может быть построен на совершенно других принципах. Кмк, такое сравнение тут не совсем к месту.Самым монструозным из всех был конечно Smarty. Мне казалось, что люди сошли с ума. Как иначе можно было объяснить желание написать шаблонизатор… для шаблонизатора Perl?
Конечно же автор имеет в виду php, а не perl. Фраза о «шаблонизаторе для шаблонизатора» тоже модная, но слишком часто используется не в тему.11 апреля 2017 в 08:48
+2↑
↓
Автор действительно имеет в виду Perl — первая версия PHP была написана на Perl и под его сильным влиянием. $ перед каждой переменной? Это из Perl!11 апреля 2017 в 09:02 (комментарий был изменён)
0↑
↓
первая версия PHP была написана на Perl
Это шутка что ли или я туплю? Насколько мне помнится, php/fi сразу был написан на C.
Исправление… Понял о чем вы, о первом наборе скриптов на perl.
11 апреля 2017 в 08:53
0↑
↓
Почему MVC? Почему фреймворк? Почему ORM? И т.д. Это вовсе не необходимые компоненты бэкенда. Я бы назвал их «модными», но бэкенд может быть построен на совершенно других принципах. Кмк, такое сравнение тут не совсем к месту.
Потому что примерно 90% кода из мира веб-разработки, который я видел или поддерживал — это MVC-фрейворк + ORM и не более. На PHP — это чаще всего Active Record, на Java — Hibernate, на .NET — Entity Framework. Да, бывают другие стеки. Но мейнстрим — именно ORM + MVC = love
11 апреля 2017 в 09:13
0↑
↓
Как по мне, то для разработчика MV* ООП веб-бэкендов, не стоит начинать свой путь во фронте с React+Redux, достаточно одного React, или React+MobX, если практикуешь DDD. Redux — это полное разделение состояния и поведения, а не их инкапсуляция в объектах.
11 апреля 2017 в 09:28
0↑
↓
Action’ы redux’а — это же не доменные объекты, а скорее просто message. Что мешает ловить экшны, создающие эффекты в middleware, обрабатывать в любом удобном виде, в т.ч. с применением DDD и по завершению операции выбрасыватьDomainEventSucceeded / Failed
и уже их обрабатывать в редюсерах?11 апреля 2017 в 09:50
0↑
↓
Доменные объекты инкапсулируют данные и поведение. Redux же их разделят по крайней мере на уровне рекомендуемых практик. Может и можно технически хранить в store полноценные объекты с методами, реализующими доменное поведение, а потом дергать их в редюсерах, но точно это будет шоком для подавляющего большинства активно использующих Redux. А скорее всего эта возможность либо просто заблокирована (может только в дев-режиме), либо ожидаемой реакции на вызов методов объекта не будет, поскольку redux+react не заметят, что изменилось состояние объекта в редьюсере, поскольку ожидает либо возврат того же объекта без изменений, либо нового объекта. В общем не прокатит что-то вроде (state, value) => state.domainObject.setValue (value).
Можно отдельно вынести мутабельную модель и проецировать её состояние на redux-стор в миддлварах, но что нам это даст, кроме «потому что можем»?
11 апреля 2017 в 09:51
0↑
↓
Мешает экпоненциальная костыльность «современной front end разработки» :-)
11 апреля 2017 в 10:13
0↑
↓
Почем нет ни слова про mobX? Мы же в 201711 апреля 2017 в 10:25
0↑
↓
Где relay?11 апреля 2017 в 10:30
0↑
↓
Про reselect забыли!