Redux - это фреймворк React, который вам рекомендуют другие фреймворки flux. Когда я начал писать это, она была 1.0.0 (и на момент публикации последняя версия 3.0.0!), И пока еще рано говорить о том, как ее использовать.

Его автор, Дэн Абрамов, собрал некоторую отличную документацию, но она еще не полностью описывает, как работать с масштабной разработкой Redux - люди начинают спрашивать, есть ли какие-нибудь репозитории нетривиальных? приложения, использующие redux . Что ж, надеюсь, это к чему-то приведет.

Мы поговорим о:

  • полный стек технологий
  • что делает каждая часть Redux
  • как выложить проект Redux
  • как обрабатывать асинхронные данные с помощью WebSockets.

Что я уже должен знать?

Прочтите документацию redux.

Вы должны были прочитать статью Дэна о Умных и глупых компонентах.

Какие инструменты вам нужны для проекта Redux?

Redux - это не просто Redux. Это целая куча вещей, которые объединяются вместе, некоторые из которых являются официальными и v1.0.0, а некоторые из них все еще вынашиваются из детства.

Ваш набор инструментов, вероятно, будет содержать большую часть следующего:

  • Webpack
    Используйте this, чтобы собрать ваши пакеты, вместо Browserify или Require и всего того, что вы используете. Почему? Потому что часть начальной демонстрации Redux показала его мощь при горячей перезагрузке. И это построено на Webpack.
    Так приятно иметь возможность просто нажать Сохранить и увидеть обновление стиля, вместо того, чтобы сохранять, обновлять, переходить к нужному экрану и повторять.
  • Babel
    Отчасти потому, что Redux настолько функциональный, и вы можете так много использовать новый сахар ES6 / 7, что это дает вам, но отчасти потому, что HotReloading теперь написан как плагин Babel!
  • Реагировать
    Ага. Но мы должны сказать, что хотя версия 0.13 является стабильной на момент публикации этой статьи, мы все ждем версии 0.14 по той причине, что некоторые проблемы, связанные с контекстом, были исправлены.
    Поскольку ES6 теперь предоставляет нам правильные классы, React осуждает использование миксинов. Чтобы обойти это, вместо этого используйте компоненты высшего порядка, которые объединяют ваши компоненты React в родительский элемент, который может предоставлять информацию через новый (своего рода секрет) механизм контекста. Redux полностью использует это.
  • Redux
    Double Duh.
  • React-Redux
    Хотя, строго говоря, Redux не зависит от фреймворка, он был написан с учетом React. Это обеспечивает компоненты более высокого порядка, которые связывают компоненты React с хранилищами Redux.
  • Промежуточное ПО
    Здесь есть два варианта: преобразователи или какая-то разновидность библиотеки обещаний. В любом случае вам нужна асинхронность в вашем приложении, и это тот бит, который позволяет сделать это в ваших создателях действий.
  • Библиотека запросов
    Для этого асинхронного кода есть причина. Я использовал для этого Axios - он обещан на основе, поэтому хорошо сочетается с промежуточным программным обеспечением обещаний.
  • React-Router (-redux)
    Маршрутизаторы якобы предназначены для обновления панели инструментов, чтобы показать, где вы находитесь в приложении. Но более фундаментальная причина для них заключается в том, что он дает логический механизм для разбиения вашего кода.
    Проблема с маршрутизатором в том, что он дает вам больше состояния, которого нет в ваших магазинах. Войдите в Redux-Router. Он гарантирует, что состояние находится в Redux, поэтому теперь вы можете, например, отправлять события из ваших SmartComponents на основе проекта верхнего уровня, в котором вы находитесь (то есть в вашей спецификации маршрута).

Как вы используете разные части Redux?

Все мы знаем историю о том, что Flux - это однонаправленная структура данных. Но даже все же, как вы его используете?

В любом приложении вам необходимо:

  • Получите начальное состояние
  • Нарисуйте вещи на основе этого состояния
  • Обработка взаимодействия с пользовательским интерфейсом
  • Делайте запросы и поддерживайте состояние своих хранилищ данных.
  • Обновляйте и перерисовывайте вещи.

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

Я организовал свой код следующим образом:

Используйте маршруты, чтобы убедиться, что у вас есть правильные данные для ваших компонентов

Это хорошо, потому что отделяет рендеринг от сбора данных. Используйте метод onEnter маршрута для выполнения команд, чтобы собрать то, что необходимо для рендеринга. Вам не нужен этот метод, чтобы на самом деле ждать завершения сбора, потому что вы можете…

Используйте SmartComponents, чтобы убедиться, что ваши DumbComponents готовы к рендерингу

Ваши SmartComponents должны быть компонентами вашего Маршрута. В функции рендеринга вашего SmartComponent есть метод, который проверяет наличие необходимых данных:

render () {
  if (this.hasData()) {
    return this.renderComponents();
  } else {
    return this.renderLoadingScreen();
  }
}

Используйте SmartComponents для максимально возможной предварительной обработки, чтобы ваши DumbComponents были действительно тупыми.

Например, когда вы передаете обработчик своему DumbComponent, каррируйте его с помощью идентификатора, чтобы DumbComponent его не знал:

renderComponents () {
  return <DumbComponent 
    onSelect={this.itemSelected.bind(this, this.props.item.id)}
  >;
}

Используйте DumbComponents для рендеринга * всего *.

Даже не добавляйте ‹div› в SmartComponent. Он должен только когда-либо составлять DumbComponents для вас. Разделите свои заботы - и не начинайте делать «одну мелочь»!

Используйте SmartComponents для создания создателей действий.

Когда DumbComponent взаимодействует с пользователем, он не должен сам обрабатывать какую-либо логику - он должен вслепую вызывать функцию, переданную ему SmartContainer, и позволять ему выполнять работу.

Затем SmartContainer должен упорядочить необходимые данные и передать их создателю действия.

Используйте ActionCreators для преобразования структур данных приложения в структуры данных API.

Создатели ActionCreators несут ответственность за преобразование данных из формата вашего приложения в формат API, с которым вы общаетесь. Это в обоих направлениях - при запросе или обработке ответа.

Поскольку вывод действия обрабатывается редуктором, который не знает, как оно было вызвано, вы можете обнаружить, что иногда вы не можете просто вернуть результат вызова API - вам нужно обогатить его дополнительными данными, например: if ваше действие - PROJECT_UPDATE, и вы передаете новое имя проекта и идентификатор проекта, а API просто возвращает {savedAt: «‹ some date ›»}, тогда вам нужно будет передать данные из аргументы в ответ:

function updateProject(projectId, projectName) {
  request.put(`/project/${projectId}`, {projectName}).then(
    response => Object.assign(
      {projectId, projectName}, 
      response.data
    )
  );
}

Используйте редукторы, чтобы синхронизировать состояние.

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

switch (action.type) {
   ...
   case USER_LOGOUT: 
     return {}
}

Макет файла

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

Я обнаружил, что в приложении Redux есть две отдельные задачи, и я организовываю свои файлы вокруг них.

Одна из проблем - данные. Ваши действия (которые генерируют запросы на изменение данных) могут быть вызваны из любого места (обычно из SmartComponents). Однако ваши редукторы (которые выполняют изменение данных) привязаны к вашим действиям. Действия могут быть сгруппированы вместе по темам в вашем приложении: возможно, одна часть связана с входом в систему и разрешениями пользователя. Другой может иметь отношение к проектам, которыми они управляют. Все эти вещи создаются, извлекаются, обновляются и удаляются - а также, возможно, множество других действий - и все это должно существовать вместе.

С другой стороны, у вас есть макет вашего приложения, ориентированный на view - маршруты к различным представлениям, SmartComponents, которые собирают данные и обрабатывают взаимодействия, и DumbComponents, которые их визуализируют.

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

Таким образом, я размещаю свои проекты так:

public/
  index.html
  client/
    index.js
    modules/
      reducers.js
      users/
        constants.js
        actions/
          user_fetch.js
          user_login.js
          permissions_fetch.js
        reducers/
          index.js
          user.js
          permission.js
      projects/
    routes/
      login/
        index.js
        containers/
          login.js
        components/
          login.js
      logged_in/
      project_list/
      project_view/

Одноразовые папки выделены полужирным шрифтом, папки с аналогичным макетом - курсивом.

Каталог modules отвечает за хранение файлов, связанных с данными, с разбивкой по подкаталогам, которые имеют дело с аспектами вашего приложения. Вы можете потенциально иметь их как отдельные репозитории npm, которые вы включаете в свой проект - они не имеют никаких зависимостей, кроме redux и сторонних библиотек.

Для каждого действия и редуктора есть отдельный файл. Существует один стандарт, который пытается собрать все содержимое модуля в один файл. Я лично выступаю против этого в проектах среднего и крупного размера - когда дела становятся большими, лучше разбить их на как можно меньшие части!

Чтобы сделать редукторы похожими по структуре для разных модулей, включен файл index.js, который экспортирует все остальные редукторы в папке. Затем на верхнем уровне существует единственный файл reducers.js, который включает все ‹module-name› / reducer. Затем этот единственный файл редукторов можно использовать для создания вашего хранилища Redux.

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

  • SmartComponents в каталоге контейнеров,
  • DumbComponents в каталоге компонентов и
  • Файл index.js, содержащий Маршрут.

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

Ваш файл маршрута также может быть хранителем данных в вашем магазине, когда вы переходите к маршруту, используя методы onEnter и onLeave. Здесь вы можете отправлять действия выборки, которые гарантируют, что ваши компоненты будут иметь нужные данные. Это действительно полезно, когда у вас есть глубоко вложенные маршруты.
Например, учитывая маршрут / app / project / 10 / permission, вы можете:

  • в / app получить данные для входа текущего пользователя
  • в / project получить список проектов, доступных пользователю
  • in / 10 позволяет получить мелкозернистые детали проекта 10
  • в / permission получить список разрешений, доступных пользователю для установки

Затем, при переходе на другой маршрут, / app / project / 11, вы будете выполнять выборку только для тех вещей, которые изменились (обработчик маршрута для / 11), поэтому вы будете выполнять только одну выборку. для Проекта 11. Это можно сделать следующим образом:

import Projects from "./containers/projects";
import ProjectDetailRoute from "routes/project_detail";
export default class ProjectList {
  constructor () {
    this.path = "project";
    this.projectDetailRoute = new ProjectDetailRoute();
  }
  getChildRoutes (state, cb) {
    cb(null, [this.projectDetailRoute]);
  }
  getComponents (cb) {
    cb(null, ProjectTasks);
  }
  onEnter () {
    this.fetchProjects();
  }
  fetchProjects () {
    ...
  }
}

Как назвать свой XXX

Называть вещи сложно. Но то же самое и с управлением состоянием, и Redux избавляет от этой боли, поэтому можем ли мы также называть вещи просто?

Действия: назовите их ‹noun› - ‹verb›, например Project-Create, User-Login. Причина этого в том, что вы хотите, чтобы они были сгруппированы по типу объекта, а не по типу действия.

Редукторы: назовите их ‹noun›.

Как вы обрабатываете асинхронные данные из внешних источников?

Очевидно, существует One True Flow (Действие - ›Редуктор -› SmartContainer - ›DumbComponent). Но как именно в это вписывается ваше изменение?

Сторонние асинхронные данные обычно поступают из канала WebSocket. Вам нужно будет слушать это только в некоторых частях вашего приложения - например, когда вы вошли в систему или на определенной веб-странице. Более того, способ обработки действий с точки зрения пользовательского интерфейса заключается в том, что пользователь генерирует событие, DumbComponent передает это событие в SmartComponent, а затем генерирует действие. Ваш SmartComponent помещается на страницу Маршрутом.

В этом случае нет DumbComponent для рендеринга, однако есть Маршрут, который определяет, когда вы должны получать доступ к данным, и Действие, которое вводит данные в Redux. Недостающее звено - это SmartComponent, который генерируется Route для обработки входящих действий и преобразования их в события. Этому SmartComponent не потребуется какой-либо DumbComponent для визуализации чего-либо пользователю, но он, вероятно, должен быть независимым от другого SmartComponent, который это делает.

React-Router прекрасно справляется с этим:

Маршрут может иметь несколько именованных компонентов,

getComponents () {
  cb(null, {view: ViewContainer, data: DataContainer};
}

SmartComponent, который затем визуализирует компоненты в этом маршруте, может затем отображаться следующим образом:

render () {
  return <div>{this.props.view}{this.props.data}</div>
}

Затем DataContainer может реагировать на изменения в свойствах (возможно, прослушивать другой канал или направлять данные в другое место) с помощью componentDidUpdate и закрывать соединение с помощью componentWillUnmount.

Заключение

Я сижу на этом пару недель, так как всегда есть еще кое-что, что можно добавить - это не история, у которой есть конец, - но я выкидываю это, чтобы люди, плохо знакомые с Redux, могли получить что-то вне времени Тусуюсь на Redux канале Reactiflux! Прокомментируйте и аннотируйте это, и я сохраню эту статью как полуживую и дышащую в течение следующих нескольких недель!