React – Flux & Redux. Пошаговое Руководство, Часть 1
Введение
Когда я начал изучать Redux, то осознал, что накопленные мной знания о Flux через различные статьи об этом подходе и мой личный опыт не совсем корректен. Я не имею ввиду, что изученные статьи о Flux плохо написаны, но тем не менее, корректного понимания так и не удалось добиться. В конце концов, я стал просто применять документацию различных Flux-фреймворков и пытаться подогнать их под теоретическую концепцию, о которой я читал (actions / actions creators, store, dispatcher и т.д.). Только тогда, когда начал изучать Redux, я понял, что Flux – гораздо проще, чем мне казалось. Это произошло благодаря тому, что Redux сам по себе был очень хорошо спроектирован и лишен множества анти-шаблонных особенностей, которые представлены в достаточном количестве во многих других фреймворках, которые были мной испробованы. И мне кажется, что на сегодняшний день Redux – один из лучших способов изучить Flux, чем пытаться это сделать с помощью каких-либо других фреймворков. Именно поэтому, мне бы хотелось поделиться с другими людьми концепцией Flux, описав её своими словами, ориентируясь на использование Redux.
Оглавление
- Часть 1
- Часть 2
- 04. Простой Редуктор
- 05. Получение Состояния
- 06. Объединение Редукторов
- 07. Отправка Действия
- Часть 3
- Часть 4
- 12. Практический Пример
- 13. Заключение
Вам могла быть знакома эта диаграмма, представляющая знаменитый однонаправленный поток данных в Flux-приложении:
В этом туториале мы постепенно будем знакомиться со всеми понятиями, которые представлены на диаграмме. И вместо того, чтобы пытаться объяснить данную диаграмму в целом и отображенный на ней поток данных в общем, мы возьмем каждую её часть отдельно и попытаемся понять, зачем нужна эта часть и какую роль она играет. В итоге, после понимания всех частей, вы увидите, что эта диаграмма идеально описывает нашу тему.
Но прежде, чем мы начнем, давайте поговорим немного о том, зачем существует Flux и зачем он нам нужен… Давайте представим, что создаем веб-приложение. Из чего состоит любое веб-приложение?
- Шаблоны / HTML = Views
- Данные, которыми заполнятся наши шаблоны = Models
- Логика получения данных, совмещение всех шаблонов вместе и правильная реакция на пользовательские события, изменения данных и т.п. = Controller
Все это является классическим MVC-паттерном, который всем нам хорошо известен. Но это также выглядит, как и Flux-концепция, только выраженная слегка иным способом:
- Модель – это хранилище
- Пользовательские события, изменения данных и их обработчики – это "action creators" -> action -> dispatcher -> callback
- Шаблоны – это, например, React шаблоны или что угодно, связанное с Flux
Таким образом, получается, что Flux – это всего лишь новое слово в словаре? Не совсем так. Определив новые слова или термины, мы можем выразить более точно те разнообразные понятия, которые мы поместили в эти термины. К примеру, является ли выборка данных действием? А каким действием является клик мыши или изменение в поле ввода? И когда наше приложение производит какие-либо действия, мы просто их называем соответствующим термином. Далее, вместо того, чтобы обрабатывать все эти действия в Моделях или Шаблонах, Flux берет их и пропускает сначала через то, что зовется dispatcher, затем через наше хранилище данных и под конец оповещает всех наблюдателей за данными.
Для большего прояснения различий между MVC и Flux мы разберем стандартный случай в MVC-приложении:
- Пользователь кликает на кнопке “А”
- Обработчик на кнопке “А” производит изменение в Модели “А”
- Обработчик в Модели “А” производит изменение в Модели “Б”
- Обработчик в Модели “Б” производит изменение в Шаблоне “Б”, который заново рендерит себя
Очень быстро может сложиться ситуация в таком окружении, когда поиск источника бага может оказаться весьма непростой задачей. Это происходит по той причине, что всякий Шаблон может наблюдать за любой Моделью, а Модель может наблюдать за несколькими другими Моделями, таким образом, данные могут поступать из самых разных мест и изменены множеством источников (любым Шаблоном или Моделью).
Тогда как при использовании Flux и его однонаправленного потока данных, пример приведенный выше, может превратиться в следующий вариант:
- Пользователь кликает на кнопке “А”
- Обработчик на кнопке “А” производит действие, которое является dispatcher и производит изменения в хранилище “А”
- Так как все остальные хранилища так же оповещены о действии, то хранилище “Б” может тоже реагировать на действие
- Шаблон “Б” получает уведомление об изменении в хранилище А и Б, и производит обновление себя
Каким образом мы избежали прямого связывания хранилища А с хранилищем Б? Дело в том, что каждое хранилище может быть модифицировано только действием и ничем иным. И как только все хранилища ответили на действие, шаблон может обновиться. Таким образом, поток данных всегда будет односторонним:
действие -> хранилище -> шаблон -> действие -> хранилище -> шаблон -> действие -> ...
Раз уж мы рассмотрели случай с действием, тогда давайте продолжим наш туториал с action и action creators.
Простой Action Creator
В введении мы немного поговорили о действиях, но что точно значат эти “action creator” и каким образом они связаны с “actions”?
На самом деле, это довольно-таки простой вопрос, который может быть раскрыт буквально несколькими строчками кода:
var actionCreator = function() {
// ...эта функция создает действие, что и указано в её названии, а затем возвращает это действие
return {
type: 'AN_ACTION'
}
}
И это все? Да.
Тем не менее, стоит обратить внимание на одну особенность – формат действия. Это некая разновидность соглашения
в Flux, при котором действие является объектом, содержащим свойство type
. Свойство
type
позволит произвести
дальнейшую обработку действия. И самом собой, действие может содержать другие свойства, чтобы передать любые
необходимые вам данные.
Позднее, мы разберем возможность action creator возвращать что-то отличное от действия, например, функцию. Это будет особо полезно для асинхронной обработки действий (подробнее об этом в главе Отправка Асинхронного Действия 1).
Теперь мы можем вызвать action creator и получить ожидаемое действие:
console.log( actionCreator() );
Output: { type: 'AN_ACTION' }
Хорошо, это работает, но ничего толкового при этом не происходит… Что нам нужно сделать сейчас, так это чтобы наше действие было отослано всем тем сущностям, которые заинтересованы в нём и которые могут среагировать на это действие соответствующим образом. Этот процесс называется “Отправкой действия”.
И как бы сказал Капитан Очевидность, для отправки действия, нам понадобится… функция для отправки действия. И чтобы дать знать всем заинтересованным сущностям от том, что действие произошло, нам нужен некий механизм для регистрации подписчиков на это действие.
Таким образом, на данный момент, поток нашего приложения будет иметь следующий вид:
ActionCreator -> Action
Более подробно ознакомиться с actions и action creators можно в официальном руководстве.
Простой Подписчик
Для начала давайте создадим нашего теоретического подписчика:
var mySubscriber = function() {
console.log( 'Что-то произошло' );
// Сделать что-нибудь
}
Всё просто, не так ли? Тем не менее, этот подписчик все еще нигде не зарегистрирован, но вскоре мы это сделаем.
И когда он будет вызван, произойдет то, для чего он был разработан (в нашем случае будет вызван console.log()
метод).
mySubscriber();
Теперь поток нашего приложение будет иметь такой вид:
ActionCreator -> Action ... Subscriber
Встречаем Redux и немного о Состоянии
Иногда те действия, которые мы будем обрабатывать в нашем приложении, могут быть созданы не только для оповещения того, что что-то произошло, но также и для того, чтобы сказать нам о необходимости обновления определенных данных.
И это является довольно-таки сложной задачей для любого приложения.
- Где мне хранить все данные, относящиеся к моему приложения на протяжении его жизненного цикла?
- Как мне произвести обработку изменений таких данных?
- Как мне распространить изменения на все части моего приложения?
Здесь в дело вступает Redux
Redux - это предсказуемый контейнер состояния данных для JavaScript приложений.
Давайте возьмем озвученные выше вопросы и ответим на них в терминах Redux (а также и в терминах Flux для некоторых из них):
- Где мне хранить все данные, относящиеся к моему приложения на протяжении его жизненного цикла?
- Вы храните их любым удобным для вас способом, например, в JS объектах, массивах, Immutable структурах и т.п.
- Данные вашего приложения будут называться состоянием. Это имеет смысл, так как данные приложения будут развиваться с течением времени, поэтому, уместно их назвать состоянием приложения. Но это состояние можно передать в Redux (Redux, по сути – контейнер состояния).
- Как мне произвести обработку изменений таких данных?
- Используя редуктор (reducer). Редуктор является подписчиком на действия и это обыкновенная функция, которая принимает текущее состояние ваше приложения, действие и возвращает новое измененное состояние
- Как мне распространить изменения на все части моего приложения?
- Используя подписчиков для изменений состояния
Redux связывает вместе все это для нас. Подводя итог, Redux предоставляет нам:
- Место, куда можно поместить состояние нашего приложения
- Механизм подписки на обновления состояния
- Механизм отправки действий в модификаторы вашего приложения – редукторы
Экземпляр Redux называется хранилищем и может быть вызван следующим образом:
import { createStore } from 'redux';
var store = createStore();
Но при запуске такого кода можно получить такую ошибку:
Error: Invariant Violation: Expected the reducer to be a function.
Это происходит по той причине, что createStore
ожидает, как минимум, функцию, для того, чтобы произвести
редуцирование состояния приложения. Изменим код:
import { createStore } from 'redux';
var store = createStore( () => {} );
Кажется, теперь все в порядке.
Часть 2 →
При создании статьи были использованы следующие источники: