Навигация по экосистеме React.js

Опубликовано: 2022-03-11

навигация по экосистеме react.js

Скорость инноваций в JavaScript Land настолько высока, что некоторые даже считают ее контрпродуктивной. Библиотека может превратиться из игрушки, которую рано освоили, в современную и морально устаревающую в течение нескольких месяцев. Способность определить инструмент, который останется актуальным еще как минимум год, сама по себе становится искусством.

Когда два года назад был выпущен React.js, я только изучал Angular и быстро отмахнулся от React как от какой-то малоизвестной еще одной библиотеки шаблонов. За эти два года Angular действительно закрепился среди разработчиков JavaScript и почти стал синонимом современной разработки JS. Я даже начал видеть Angular в очень консервативной корпоративной среде и воспринимал его светлое будущее как должное.

Но вдруг случилось странное. Похоже, Angular стал жертвой эффекта Осборна, или «смерти по предварительному объявлению». Команда объявила, что, во-первых, Angular 2 будет совершенно другим, без четкого пути миграции с Angular 1, и, во-вторых, что Angular 2 не будет доступен еще год или около того. Что это говорит тому, кто хочет начать новый веб-проект? Вы хотите написать свой новый проект в среде, которая устареет с выпуском новой версии?

Это беспокойство среди разработчиков сыграло на руку React, который стремился утвердиться в сообществе. Но React всегда позиционировал себя как «V» в «MVC». Это вызвало некоторое разочарование среди веб-разработчиков, которые привыкли работать с полными фреймворками. Как заполнить недостающие фрагменты? Должен ли я написать свой собственный? Должен ли я просто использовать существующую библиотеку? Если да, то какой?

Конечно же, у Facebook (создатели React.js) был еще один козырь в рукаве: рабочий процесс Flux, который обещал восполнить недостающие функции «M» и «C». Чтобы сделать ситуацию еще более интересной, Facebook заявил, что Flux — это «шаблон», а не фреймворк, и их реализация Flux — лишь один из примеров шаблона. Верный своему слову, их реализация была очень упрощенной и включала в себя написание большого количества многословных повторяющихся шаблонов, чтобы все заработало.

На помощь пришло сообщество open source, и спустя год у нас есть десятки библиотек Flux и даже несколько метапроектов, направленных на их сравнение. Это хорошая вещь; вместо того, чтобы выпустить какой-то готовый корпоративный фреймворк, Facebook удалось подогреть интерес в сообществе и побудить людей придумывать собственные решения.

У этого подхода есть один интересный побочный эффект: когда вам нужно объединить множество разных библиотек, чтобы получить полную структуру, вы эффективно избегаете привязки к поставщику, и инновации, которые появляются во время создания вашей собственной среды, могут быть легко повторно использованы. в другом месте.

Вот почему новые вещи, связанные с React, так интересны; большую часть его можно легко повторно использовать в других средах JavaScript. Даже если вы не планируете использовать React, взгляд на его экосистему вдохновляет. Возможно, вы захотите упростить свою систему сборки с помощью мощного, но сравнительно простого в настройке сборщика модулей Webpack или начать писать ECMAScript 6 и даже ECMAScript 7 уже сегодня с помощью компилятора Babel.

В этой статье я расскажу о некоторых интересных функциях и доступных библиотеках. Итак, давайте изучим экосистему React!

Человек-исследователь реакции

Система сборки

Возможно, система сборки — это первое, о чем вы должны позаботиться при создании нового веб-приложения. Система сборки — это не только инструмент для запуска скриптов, в мире JavaScript она обычно формирует общую структуру вашего приложения. Наиболее важные задачи, которые должна решать система сборки, следующие:

  • Управление внешними и внутренними зависимостями
  • Запуск компиляторов и препроцессоров
  • Оптимизация активов для производства
  • Запуск веб-сервера разработки, средства просмотра файлов и средства перезагрузки браузера.

В последние годы рабочий процесс Yeoman с Bower и Grunt был представлен как святая троица современной фронтенд-разработки, решающая проблемы генерации шаблонов, управления пакетами и выполнения общих задач соответственно, а более прогрессивные люди недавно перешли с Grunt на Gulp.

В среде React об этом можно смело забыть. Не то, чтобы вы не могли их использовать, но есть вероятность, что вам сойдет с рук использование Webpack и старого доброго NPM. Как это возможно? Webpack — это сборщик модулей, который также реализует синтаксис модуля CommonJS, распространенный в мире Node.js, и в браузере. На самом деле это упрощает работу, поскольку вам не нужно изучать еще один менеджер пакетов для внешнего интерфейса; вы просто используете NPM и делитесь зависимостями между сервером и внешним интерфейсом. Вам также не нужно решать проблему загрузки файлов JS в правильном порядке, потому что это выводится из импорта зависимостей, указанного в каждом файле, и весь рой правильно объединяется в один загружаемый скрипт.

Логотип веб-пакета
Вебпак

Чтобы сделать его еще более привлекательным, Webpack, в отличие от своего старшего брата Browserify, может работать и с другими типами ресурсов. Например, с помощью загрузчиков вы можете преобразовать любой файл ресурсов в функцию JavaScript, которая либо встраивает, либо загружает указанный файл. Так что забудьте о ручной предварительной обработке и ссылках на активы из HTML. Просто require свои файлы CSS/SASS/LESS из JavaScript, а Webpack позаботится обо всем остальном с помощью простого файла конфигурации. Webpack также включает в себя веб-сервер разработки и средство просмотра файлов. Кроме того, вы можете использовать ключ "scripts" в package.json для определения однострочных команд оболочки:

 { "name": "react-example-filmdb", "version": "0.0.1", "description": "Isomorphic React + Flux film database example", "main": "server/index.js", "scripts": { "build": "./node_modules/.bin/webpack --progress --stats --config ./webpack/prod.config.js", "dev": "node --harmony ./webpack/dev-server.js", "prod": "NODE_ENV=production node server/index.js", "test": "./node_modules/.bin/karma start --single-run", "postinstall": "npm run build" } ... }

И это все, что вам нужно для замены Gulp и Bower. Конечно, вы по-прежнему можете использовать Yeoman для создания шаблонов приложений. Не расстраивайтесь, если нет генератора Yeoman для того, что вам нужно (самые передовые библиотеки часто не имеют его). Вы все еще можете просто клонировать какой-нибудь шаблон с GitHub и взломать его.

Связанный: Как компоненты React упрощают тестирование пользовательского интерфейса

ECMAScript завтрашнего дня сегодня

Темпы разработки языка JavaScript значительно увеличились в последние годы, и после периода устранения особенностей и стабилизации языка мы теперь видим появление новых мощных функций. Проект спецификации ECMAScript 6 (ES6) был завершен, и хотя он еще не обнародован, он уже находит широкое распространение. Работа над ECMAScript 7 (ES7) продолжается, но многие из его функций уже применяются в наиболее передовых библиотеках.

Логотип ECMAScript 7
ECMAScript 7

Как это возможно? Возможно, вы думаете, что не сможете воспользоваться преимуществами этих блестящих новых функций JavaScript, пока они не будут поддерживаться в Internet Explorer, но подумайте еще раз. Транспиляторы ES уже стали настолько повсеместными, что мы можем обойтись даже без должной поддержки браузеров. Лучший транспилятор ES, доступный прямо сейчас, — это Babel: он возьмет ваш новейший код ES6+ и преобразует его в ванильный ES5, так что вы сможете использовать любую новую функцию ES, как только она будет изобретена (и реализована в Babel, что обычно происходит достаточно быстро). быстро).

Логотип Вавилона
Вавилон

Новейшие функции JavaScript полезны во всех интерфейсных фреймворках, а React недавно был обновлен, чтобы хорошо работать со спецификациями ES6 и ES7. Эти новые функции должны устранить множество головных болей при разработке с помощью React. Давайте рассмотрим некоторые из наиболее полезных дополнений и то, как они могут принести пользу проекту React. Позже мы увидим, как использовать некоторые полезные инструменты и библиотеки с React, используя этот улучшенный синтаксис.

Классы ES6

Объектно-ориентированное программирование — это мощная и широко принятая парадигма, но подход JavaScript несколько экзотичен. Таким образом, большинство интерфейсных фреймворков, будь то Backbone, Ember, Angular или React, приняли собственные проприетарные способы определения классов и создания объектов. Но с ES6 у нас теперь есть традиционные классы в JavaScript, и просто имеет смысл использовать их вместо написания собственной реализации. Итак, вместо:

 React.createClass({ displayName: 'HelloMessage', render() { return <div>Hello {this.props.name}</div>; } })

мы можем написать:

 class HelloMessage extends React.Component { render() { return <div>Hello {this.props.name}</div>; } }

Для более сложного примера рассмотрим этот код с использованием старого синтаксиса:

 React.createClass({ displayName: 'Counter', getDefaultProps: function(){ return {initialCount: 0}; }, getInitialState: function() { return {count: this.props.initialCount} }, propTypes: {initialCount: React.PropTypes.number}, tick() { this.setState({count: this.state.count + 1}); }, render() { return ( <div onClick={this.tick}> Clicks: {this.state.count} </div> ); } });

И сравните с версией ES6:

 class Counter extends React.Component { static propTypes = {initialCount: React.PropTypes.number}; static defaultProps = {initialCount: 0}; constructor(props) { super(props); this.state = {count: props.initialCount}; } state = {count: this.props.initialCount}; tick() { this.setState({count: this.state.count + 1}); } render() { return ( <div onClick={this.tick.bind(this)}> Clicks: {this.state.count} </div> ); } }

Здесь методы жизненного цикла React getDefaultProps и getInitialState больше не нужны. getDefaultProps становится переменной статического класса defaultProps , а начальное состояние просто определяется в конструкторе. Единственным недостатком является то, что методы больше не являются автопривязываемыми, поэтому при вызове обработчиков из JSX приходится использовать bind .

Декораторы

Декораторы — полезная функция ES7. Они позволяют расширить поведение функции или класса, поместив их внутрь другой функции. Например, предположим, что вы хотите иметь один и тот же обработчик изменений для некоторых ваших компонентов, но не хотите фиксировать антишаблон наследования. Вместо этого вы можете использовать декоратор класса. Давайте определим декоратор следующим образом:

 addChangeHandler: function(target) { target.prototype.changeHandler = function(key, attr, event) { var state = {}; state[key] = this.state[key] || {}; state[key][attr] = event.currentTarget.value; this.setState(state); }; return target; }

Здесь важно то, что функция addChangeHandler добавляет функцию changeHandler к прототипу целевого класса.

Чтобы применить декоратор, мы могли бы написать:

 MyClass = changeHandler(MyClass)

или более элегантно, с синтаксисом ES7:

 @addChangeHandler class MyClass { ... }

Что касается содержимого самой функции changeHandler , с отсутствием в React двусторонней привязки данных работа с входными данными в React может быть утомительной. Функция changeHandler пытается упростить эту задачу. Первый параметр указывает key объекта состояния, который будет служить объектом данных для ввода. Второй параметр — атрибут, в который будет сохранено значение из поля ввода. Эти два параметра задаются из JSX с помощью ключевого слова bind .

 @addChangeHandler class LoginInput extends React.Component { constructor(props) { super(props); this.state = { login: {} }; } render() { return ( <input type='text' value={this.state.login.username} onChange={this.changeHandler.bind(this, 'login', 'username')} /> <input type='password' value={this.state.login.password} onChange={this.changeHandler.bind(this, 'login', 'password')} /> ) } }

Когда пользователь изменяет поле имени пользователя, его значение сохраняется в this.state.login.username без необходимости определения дополнительных пользовательских обработчиков.

Стрелочные функции

Динамический контекст this в JavaScript был постоянной проблемой для разработчиков, потому что несколько неинтуитивно контекст this вложенной функции сбрасывается в глобальный даже внутри класса. Чтобы это исправить, обычно необходимо сохранить this в какой-нибудь внешней переменной области видимости (обычно _this ) и использовать во внутренних функциях:

 class DirectorsStore { onFetch(directors) { var _this = this; this.directorsHash = {}; directors.forEach(function(x){ _this.directorsHash[x._id] = x; }) } }

С новым синтаксисом ES6 function(x){ можно переписать как (x) => { . Это определение метода «стрелка» не только правильно привязывает this к внешней области видимости, но и значительно короче, что определенно имеет значение при написании большого количества асинхронного кода.

 onFetch(directors) { this.directorsHash = {}; directors.forEach((x) => { this.directorsHash[x._id] = x; }) }

Деструктуризация назначений

Присваивания деструктурирования, введенные в ES6, позволяют вам иметь составной объект в левой части присваивания:

 var o = {p: 42, q: true}; var {p, q} = o; console.log(p); // 42 console.log(q); // true

Это хорошо, но как это на самом деле помогает нам в React? Рассмотрим следующий пример:

 function makeRequest(url, method, params) { var config = { url: url, method: method, params: params }; ... }

С деструктурированием вы можете сэкономить несколько нажатий клавиш. Литералу ключей {url, method, params} автоматически присваиваются значения из области видимости с теми же именами, что и у ключей. Эта идиома используется довольно часто, и устранение повторений делает код менее подверженным ошибкам.

 function makeRequest(url, method, params) { var config = {url, method, params}; ... }

Деструктуризация также может помочь вам загрузить только подмножество модуля:

 const {clone, assign} = require('lodash'); function output(data, optional) { var payload = clone(data); assign(payload, optional); }

Аргументы: Default, Rest и Spread

Аргументы функций более мощные в ES6. Наконец, вы можете установить аргумент по умолчанию :

 function http(endpoint, method='GET') { console.log(method) ... } http('/api') // GET

Устали возиться с громоздким объектом arguments ? С новой спецификацией вы можете получить остальные аргументы в виде массива:

 function networkAction(context, method, ...rest) { // rest is an array return method.apply(context, rest); }

И если вам не нравится вызывать apply() , вы можете просто распределить массив по аргументам функции:

 myArguments = ['foo', 'bar', 123]; myFunction(...myArguments);

Генераторы и асинхронные функции

ES6 представил генераторы JavaScript. Генератор — это, по сути, функция JavaScript, выполнение которой можно приостановить, а затем возобновить позже, запомнив свое состояние. Каждый раз, когда встречается ключевое слово yield , выполнение приостанавливается, а аргумент yield передается обратно вызывающему объекту:

 function* sequence(from, to) { console.log('Ready!'); while(from <= to) { yield from++; } }

Вот пример этого генератора в действии:

 > var cursor = sequence(1,3) Ready! > cursor.next() { value: 1, done: false } > cursor.next() { value: 2, done: false } > cursor.next() { value: 3, done: false } > cursor.next() { value: undefined, done: true }

Когда мы вызываем функцию генератора, она выполняется до первого yield , а затем останавливается. После вызова next() он возвращает первое значение и возобновляет выполнение. Каждый yield возвращает другое значение, но после третьего вызова функция-генератор завершает работу, и каждый последующий вызов next() будет возвращать { value: undefined, done: true } .

Конечно, смысл генераторов не в том, чтобы создавать сложные числовые последовательности. Захватывающей частью является их способность останавливать и возобновлять выполнение функций, что можно использовать для управления асинхронным потоком программы и, наконец, избавиться от этих надоедливых функций обратного вызова.

Чтобы продемонстрировать эту идею, нам сначала понадобится асинхронная функция. Обычно у нас была бы какая-то операция ввода-вывода, но для простоты давайте просто используем setTimeout и возвращаем обещание. (Обратите внимание, что ES6 также представил собственные промисы для JavaScript.)

 function asyncDouble(x) { var deferred = Promise.defer(); setTimeout(function(){ deferred.resolve(x*2); }, 1000); return deferred.promise; }

Далее нам нужна потребительская функция:

 function consumer(generator){ var cursor = generator(); var value; function loop() { var data = cursor.next(value); if (data.done) { return; } else { data.value.then(x => { value = x; loop(); }) } } loop(); }

Эта функция принимает любой генератор в качестве аргумента и продолжает вызывать для него next() до тех пор, пока есть значения для yield . В этом случае полученные значения являются промисами, поэтому необходимо дождаться разрешения промисов и использовать рекурсию с помощью loop() для достижения зацикливания вложенных функций.

Возвращаемое значение разрешается в обработчике then() и передается в value , которое определено во внешней области видимости и которое будет передано в вызов next(value) . Этот вызов делает значение результатом соответствующего выражения yield. Это означает, что теперь мы можем писать асинхронно без каких-либо обратных вызовов:

 function* myGenerator(){ const data1 = yield asyncDouble(1); console.log(`Double 1 = ${data1}`); const data2 = yield asyncDouble(2); console.log(`Double 2 = ${data2}`); const data3 = yield asyncDouble(3); console.log(`Double 3 = ${data3}`); } consumer(myGenerator);

Генератор myGenerator будет приостанавливаться при каждом yield , ожидая, пока потребитель предоставит разрешенное обещание. И действительно, мы увидим, что вычисленные числа появляются в консоли с интервалом в одну секунду.

 Double 1 = 2 Double 2 = 4 Double 3 = 6

Это демонстрирует основную концепцию, однако я не рекомендую вам использовать этот код в рабочей среде. Вместо этого выберите хорошо протестированную библиотеку, такую ​​как co. Это позволит вам легко писать асинхронный код с выходами, включая обработку ошибок:

 co(function *(){ var a = yield Promise.resolve(1); console.log(a); var b = yield Promise.resolve(2); console.log(b); var c = yield Promise.resolve(3); console.log(c); }).catch(function(err){ console.error(err.stack); });

Итак, в этом примере показано, как писать асинхронный код без обратных вызовов с помощью генераторов ES6. ES7 продвигает этот подход еще на один шаг вперед, вводя ключевые слова async и await и полностью устраняя необходимость в библиотеке-генераторе. С этой возможностью предыдущий пример будет выглядеть так:

 async function (){ try { var a = await Promise.resolve(1); console.log(a); var b = await Promise.resolve(2); console.log(b); var c = await Promise.resolve(3); console.log(c); } catch (err) { console.error(err.stack); } };

На мой взгляд, это облегчает работу с асинхронным кодом в JavaScript. Не только в React, но и везде.

Генераторы не только более лаконичны и просты, но также позволяют нам использовать методы, которые было бы очень сложно реализовать с помощью обратных вызовов. Ярким примером качества генератора является библиотека промежуточного программного обеспечения koa для Node.js. Он призван заменить Express, и для достижения этой цели у него есть одна убийственная функция: цепочка промежуточного программного обеспечения проходит не только вниз по течению (с запросом клиента), но и вверх по течению , позволяя в дальнейшем модифицировать ответ сервера. Рассмотрим следующий пример сервера koa:

 // Response time logger middleware app.use(function *(next){ // Downstream var start = new Date; yield next; // Upstream this.body += ' World'; var ms = new Date - start; console.log('%s %s - %s', this.method, this.url, ms); }); // Response handler app.use(function *(){ this.body = 'Hello'; }); app.listen(3000); 

Логотип коа
Коа

Промежуточное программное обеспечение ответа yield s ниже по потоку в обработчик ответа, который устанавливает тело ответа, а в восходящем потоке (после выражения yield ) разрешена дальнейшая модификация this.body , а также другие функции, такие как регистрация времени, что возможно. потому что восходящий и нисходящий потоки используют один и тот же контекст функции. Это намного мощнее, чем Express, в котором попытка выполнить то же самое закончится следующим образом:

 var start; // Downstream middleware app.use(function(req, res, next) { start = new Date; next(); // Already returned, cannot continue here }); // Response app.use(function (req, res, next){ res.send('Hello World') next(); }); // Upstream middleware app.use(function(req, res, next) { // res already sent, cannot modify var ms = new Date - start; console.log('%s %s - %s', this.method, this.url, ms); next(); }); app.listen(3000);

Вероятно, вы уже заметили, что здесь не так; использование «глобальной» start переменной приведет к состоянию гонки, возвращающему бессмыслицу с одновременными запросами. Решение — неочевидный обходной путь, и вы можете забыть об изменении ответа в восходящем потоке.

Кроме того, при использовании koa вы бесплатно получите асинхронный рабочий процесс генератора:

 app.use(function *(){ try { const part1 = yield fs.readFile(this.request.query.file1, 'utf8'); const part2 = yield fs.readFile(this.request.query.file2, 'utf8'); this.body = part1 + part2; } catch (err) { this.status = 404 this.body = err; } }); app.listen(3000);

Вы можете себе представить обещания и обратные вызовы, связанные с воссозданием этого небольшого примера в Express.

Логотип Node.js

Как все эти разговоры о Node.js связаны с React? Что ж, Node — это первый выбор при рассмотрении подходящей серверной части для React. Поскольку Node также написан на JavaScript, он поддерживает совместное использование кода между серверной и клиентской частью, что позволяет нам создавать изоморфные веб-приложения React. Но, об этом позже.

Библиотека потоков

React отлично подходит для создания составных компонентов представления, но нам нужен какой-то способ управления данными и состоянием во всем приложении. Почти все согласны с тем, что React лучше всего дополняется архитектурой приложений Flux. Если вы совершенно не знакомы с Flux, я рекомендую быстро обновиться.

Логотип Flux
Флюс

Что не было так универсально согласовано, так это то, какую из многих реализаций Flux выбрать. Facebook Flux был бы очевидным выбором, но для большинства людей он слишком многословен. Альтернативные реализации сосредоточены в основном на уменьшении количества требуемых шаблонов с подходом «конвенция над конфигурацией», а также с некоторыми удобными функциями для компонентов более высокого порядка, рендеринга на стороне сервера и т. д. Некоторые из главных претендентов с различными показателями популярности можно увидеть здесь. Я изучил Alt, Reflux, Flummox, Fluxxor и Marty.js.

Мой способ выбора правильной библиотеки никоим образом не объективен, но в любом случае может помочь. Fluxxor был одной из первых библиотек, которые я проверил, но сейчас она выглядит немного устаревшей. Marty.js интересен и имеет много функций, но все еще включает много шаблонов, а некоторые функции кажутся излишними. Reflux выглядит великолепно и имеет некоторую привлекательность, но кажется немного сложным для начинающих, а также не имеет надлежащей документации. Flummox и Alt очень похожи, но у Alt еще меньше шаблонов, очень активная разработка, актуальная документация и полезное сообщество Slack. Поэтому я выбрал Alt.

Альтернативный поток

Альтернативный логотип Flux
Альтернативный поток

С Alt рабочий процесс Flux становится намного проще без потери его мощности. В документации Facebook Flux много говорится о диспетчере, но мы можем игнорировать это, потому что в Alt диспетчер неявно связан с действиями по соглашению и обычно не требует никакого специального кода. Это оставляет нам только хранилища , действия и компоненты . Эти три уровня можно использовать таким образом, чтобы они хорошо отображались в мысленной модели MVC : хранилища — это модели , действия — это контроллеры , а компоненты — представления . Основное отличие заключается в однонаправленном потоке данных, центральном для шаблона Flux, что означает, что контроллеры (действия) не могут напрямую изменять представления (компоненты), а вместо этого могут только инициировать модификации модели (хранилища), к которым представления пассивно привязаны. Это уже было лучшей практикой для некоторых просвещенных разработчиков Angular.

Flux и альтернативные рабочие процессы

Рабочий процесс выглядит следующим образом:

  1. Компоненты инициируют действия.
  2. Магазины прослушивают действия и обновляют данные.
  3. Компоненты привязаны к хранилищам и перерисовываются при обновлении данных.

Действия

При использовании библиотеки Alt Flux действия обычно бывают двух видов: автоматические и ручные. Автоматические действия создаются с помощью функции generateActions и поступают напрямую в диспетчер. Ручные методы определяются как методы ваших классов действий, и они могут передаваться диспетчеру с дополнительной полезной нагрузкой. Наиболее распространенный вариант использования автоматических действий — уведомление магазинов о каком-то событии в приложении. Ручные действия, среди прочего, являются предпочтительным способом взаимодействия с сервером.

Таким образом, вызовы REST API относятся к действиям. Полный рабочий процесс выглядит следующим образом:

  1. Компонент запускает действие.
  2. Создатель действия запускает асинхронный запрос к серверу, и результат отправляется диспетчеру в качестве полезной нагрузки.
  3. Хранилище прослушивает действие, соответствующий обработчик действия получает результат в качестве аргумента, и хранилище соответствующим образом обновляет свое состояние.

Для запросов AJAX мы можем использовать библиотеку axios, которая, помимо прочего, беспрепятственно работает с данными и заголовками JSON. Вместо обещаний или обратных вызовов мы можем использовать шаблон ES7 async / await . Если статус ответа POST не равен 2XX, выдается ошибка, и мы отправляем либо возвращенные данные, либо полученную ошибку.

Давайте посмотрим на страницу входа в качестве простого примера рабочего процесса Alt. Для выхода из системы не нужно делать ничего особенного, нужно только уведомить магазин, чтобы мы могли сгенерировать его автоматически. Действие входа в систему выполняется вручную и требует данных для входа в качестве параметра для создателя действия. После того, как мы получим ответ от сервера, мы либо отправляем данные об успехе, либо, если возникает ошибка, мы отправляем полученную ошибку.

 class LoginActions { constructor() { // Automatic action this.generateActions('logout'); } // Manual action async login(data) { try { const response = await axios.post('/auth/login', data); this.dispatch({ok: true, user: response.data}); } catch (err) { console.error(err); this.dispatch({ok: false, error: err.data}); } } } module.exports = (alt.createActions(LoginActions));

магазины

Хранилище Flux служит двум целям: оно имеет обработчики действий и содержит состояние. Давайте продолжим наш пример страницы входа, чтобы увидеть, как это работает.

Давайте создадим LoginStore с двумя атрибутами состояния: user для текущего вошедшего в систему пользователя и error для текущей ошибки, связанной с входом в систему. В духе сокращения шаблонов Alt позволяет нам привязываться ко всем действиям из одного класса с помощью одной функции bindActions .

 class LoginStore { constructor() { this.bindActions(LoginActions); this.user = null; this.error = null; } ...

Имена обработчиков определяются по соглашению и добавляются on соответствующим именем действия. Таким образом, действие login обрабатывается onLogin и так далее. Обратите внимание, что первая буква имени действия будет заглавной в стиле camelCase. В нашем LoginStore у нас есть следующие обработчики, вызываемые соответствующими действиями:

 ... onLogin(data) { if (data.ok) { this.user = data.user; this.error = null; router.transitionTo('home'); } else { this.user = null; this.error = data.error } } onLogout() { this.user = null; this.error = null; } }

Компоненты

Обычный способ привязки хранилищ к компонентам — использовать какой-нибудь миксин React. Но поскольку миксины выходят из моды, должен быть какой-то другой путь. Один из новых подходов заключается в использовании компонентов более высокого порядка. Мы берем наш компонент и помещаем его внутрь компонента-обертки, который позаботится о прослушивании хранилищ и вызове повторного рендеринга. Наш компонент будет получать состояние магазина в props . Этот подход также полезен для организации нашего кода на умные и тупые компоненты, которые в последнее время стали модными. Для Alt оболочка компонента реализована AltContainer :

 export default class Login extends React.Component { render() { return ( <AltContainer stores={{LoginStore: LoginStore}}> <LoginPage/> </AltContainer> )} }

Наш компонент LoginPage также использует декоратор changeHandler представленный ранее. Данные из LoginStore используются для отображения ошибок в случае неудачного входа в систему, а о повторном рендеринге заботится AltContainer . Нажатие на кнопку входа в систему выполняет действие login в систему, завершая рабочий процесс Alt flux:

 @changeHandler export default class LoginPage extends React.Component { constructor(props) { super(props); this.state = { loginForm: {} }; } login() { LoginActions.login(this.state.loginForm) } render() { return ( <Alert>{{this.props.LoginStore.error}}</Alert> <Input label='Username' type='text' value={this.state.login.username} onChange={this.changeHandler.bind(this, 'loginForm', 'username')} /> <Input label='Password' type='password' value={this.state.login.password} onChange={this.changeHandler.bind(this, 'loginForm', 'password')} /> <Button onClick={this.login.bind(this)}>Login</Button> )} }

Изоморфный рендеринг

Изоморфные веб-приложения являются горячей темой в наши дни, потому что они решают некоторые из самых больших задач традиционных одностраничных приложений. В этих приложениях разметка создается динамически с помощью JavaScript в браузере. В результате контент недоступен для клиентов с отключенным JavaScript, в первую очередь для сканеров поисковых систем. Это означает, что ваша веб-страница не индексируется и не отображается в результатах поиска. Есть способы обойти это, но они далеки от оптимальных. Изоморфный подход пытается решить эту проблему путем предварительного рендеринга запрошенного URL-адреса одностраничного приложения на сервере. С Node.js у вас есть JavaScript на сервере, а это означает, что React также может работать на стороне сервера. Это не должно быть слишком сложно, верно?

Одним из препятствий является то, что некоторые библиотеки Flux, особенно те, которые используют синглтоны, имеют трудности с рендерингом на стороне сервера. Когда у вас есть одноэлементные хранилища Flux и несколько одновременных запросов к вашему серверу, данные будут перепутаны. Некоторые библиотеки решают эту проблему, используя экземпляры Flux, но у этого есть и другие недостатки, в частности необходимость передавать эти экземпляры в вашем коде. Alt также предлагает экземпляры Flux, но также решает проблему рендеринга на стороне сервера с помощью синглетонов; он очищает хранилища после каждого запроса, так что каждый параллельный запрос начинается с чистого листа.

Ядро функциональности рендеринга на стороне сервера обеспечивается React.renderToString . Все интерфейсное приложение React также запускается на сервере. Таким образом, нам не нужно ждать, пока клиентский JavaScript создаст разметку; он предварительно создается на сервере для доступного URL-адреса и отправляется в браузер в виде HTML. Когда клиентский JavaScript запускается, он продолжает работу с того места, на котором остановился сервер. Чтобы поддержать это, мы можем использовать библиотеку Iso, которая предназначена для использования с Alt.

Сначала мы инициализируем Flux на сервере с помощью alt.bootstrap . Можно предварительно заполнить хранилища Flux данными для рендеринга. Также необходимо решить, какой компонент отображать для какого URL, что является функцией Router на стороне клиента. Мы используем одноэлементную версию alt , поэтому после каждого рендеринга нам нужно alt.flush() хранилища, чтобы они были очищены для другого запроса. С помощью надстройки iso состояние Flux сериализуется в HTML-разметку, чтобы клиент знал, где взять:

 // We use react-router to run the URL that is provided in routes.jsx var getHandler = function(routes, url) { var deferred = Promise.defer(); Router.run(routes, url, function (Handler) { deferred.resolve(Handler); }); return deferred.promise; }; app.use(function *(next) { yield next; // We seed our stores with data alt.bootstrap(JSON.stringify(this.locals.data || {})); var iso = new Iso(); const handler = yield getHandler(reactRoutes, this.request.url); const node = React.renderToString(React.createElement(handler)); iso.add(node, alt.flush()); this.render('layout', {html: iso.render()}); });

На стороне клиента мы получаем состояние сервера и загружаем alt с данными. Затем мы запускаем Router и React.render в целевом контейнере, который при необходимости обновит сгенерированную сервером разметку.

 Iso.bootstrap(function (state, _, container) { // Bootstrap the state from the server alt.bootstrap(state) Router.run(routes, Router.HistoryLocation, function (Handler, req) { let node = React.createElement(Handler) React.render(node, container) }) })

Прекрасный!

Полезные интерфейсные библиотеки

Руководство по экосистеме React было бы неполным без упоминания нескольких интерфейсных библиотек, которые особенно хорошо работают с React. Эти библиотеки решают наиболее распространенные задачи, которые можно найти почти в каждом веб-приложении: макеты и контейнеры CSS, стилизованные формы и кнопки, проверки, выбор даты и т. д. Нет смысла изобретать велосипед, когда эти проблемы уже решены.

React-Bootstrap

Логотип React-Bootstrap

Twitter's Bootstrap framework has become commonplace since it is of immense help to every web developer who does not want to spend a ton of time working in CSS. In the prototyping phase especially, Bootstrap is indispensable. To leverage the power of bootstrap in a React application, it's good to use it with React-Bootstrap, which comes with nice React syntax and re-implements Bootstrap's jQuery plugins using native React components. The resulting code is terse and easy to understand:

 <Navbar brand='React-Bootstrap'> <Nav> <NavItem eventKey={1} href='#'>Link</NavItem> <NavItem eventKey={2} href='#'>Link</NavItem> <DropdownButton eventKey={3} title='Dropdown'> <MenuItem eventKey='1'>Action</MenuItem> <MenuItem eventKey='2'>Another action</MenuItem> <MenuItem eventKey='3'>Something else here</MenuItem> <MenuItem divider /> <MenuItem eventKey='4'>Separated link</MenuItem> </DropdownButton> </Nav> </Navbar>

Personally, I cannot escape the feeling that this is what HTML should always have been like.

If you want to use Bootstrap source with Webpack, consider using less-loader or bootstrap-sass-loader, depending on the preprocessor you prefer. It will allow you to easily customize which Bootstrap components to include, and also allow the usage of LESS or SASS global variables in your CSS code.

React Router

React Router Logo

React Router has become the de-facto standard for routing in React. It allows nested routing, support for redirections, plays nicely with isomorphic rendering, and has a simple JSX-based syntax:

 <Router history={new BrowserHistory}> <Route path="/" component={App}> <Route path="about" name="about" component={About}/> <Route path="users" name="users" component={Users} indexComponent={RecentUsers}> <Route path="/user/:userId" name="user" component={User}/> </Route> <Route path="*" component={NoMatch}/> </Route> </Router>

React Router also provides a Link component that you can use for navigation in your application, specifying only the route name:

 <nav> <Link to="about">About</Link> <Link to="users">Users</Link> </nav>

There is even a library for integration with React-Bootstrap, so if you are using Bootstrap's components and don't feel like setting the active class on them manually all the time, you can use react-router-bootstrap and write code like this:

 <Nav> <NavItemLink to="about">About</NavItemLink> <NavItemLink to="users">Users</NavItemLink> </Nav>

No additional setup is necessary. Active links will take care of themselves.

Formsy-React

Working with forms can be tedious, so let's get some help from the formsy-react library, which will help us manage validations and data models. The Formsy-React library, strangely enough, does not include the actual form inputs because users are encouraged to write their own (which is great). But if you are content with the common ones, just use formsy-react-components. Bootstrap classes are included:

 import Formsy from 'formsy-react'; import {Input} from 'formsy-react-components'; export default class FormsyForm extends React.Component { enableButton() { this.setState({canSubmit: true}); } disableButton() { this.setState({canSubmit: true}); } submit(model) { FormActions.saveEmail(model.email); } render() { return ( <Formsy.Form onValidSubmit={this.submit} onValid={this.enableButton} onInvalid={this.disableButton}> <Input name="email" validations="isEmail" validationError="This is not a valid email" required/> <button type="submit" disabled={!this.state.canSubmit}>Submit</button> </Formsy.Form> )} }

Calendar and Typeahead

Calendar and typeahead are icing on the cake of every UI toolkit. Sadly, these two components were removed from Bootstrap 3, probably because they are too specialized for a general purpose CSS framework. Luckily, I've been able to find worthy replacements in react-pikaday and react-select. I've tested more than 10 libraries, and these two came out as the best. They are dead easy to use, as well:

 import Pikaday from 'react-pikaday'; import Select from 'react-select'; export default class CalendarAndTypeahead extends React.Component { constructor(props){ super(props); this.options = [ { value: 'one', label: 'One' }, { value: 'two', label: 'Two' } ]; } dateChange(date) { this.setState({date: date}); }, selectChange(selected) { this.setState({selected: selected}); }, render() { return ( <Pikaday value={this.state.date} onChange={this.dateChange} /> <Select name="form-field-name" value={this.state.selected} options={this.options} onChange={selectChange} /> )} }
When it comes to React, it's a jungle out there! Here's a map to help you find your way.
Твитнуть

Conclusion - React.JS

In this article I've presented libraries and techniques that I consider some of the most productive in current web development. Some of them are React-specific, but due to React's open nature, many of them are usable in other environments as well. Technological progress is sometimes hindered by fear of the newest stuff, so I hope this article will help to dissipate doubts concerning React, Flux and the newest features in ECMAScript.

React Ecosystem

If you are interested, you can take a look at my example application built with these technologies. The source code is available on GitHub.

Спасибо за прочтение!

Related: React.js Best Practices and Tips by Toptal Developers