
Редакс в реальной жизни
Доклад рассказывает про реальные проблемы, с которыми вы столкнётесь при разработке приложения: как обрабатывать сайд-эффекты, куда помещать бизнес-логику и как тестировать приложение. В начале доклада — краткое введение в Редакс.
-
Это – Дэн Абрамов. У него 27К подписчиков (это круто). И в прошлом году он сделал редакс. Редакс – это библиотека для организации архитектуры приложения. -
Главные отличия редакса от остальных подходов — это то, что 1) всё состояние приложения хранится в единственном месте… -
и 2) приложение строится по однонаправленному потоку данных (http://redux.js.org/docs/basics/DataFlow.html). Однонаправленный поток кардинально отличается от MVC, где C—V и C—M обычно двунаправленные. Однонаправленный поток упрощает понимание приложения. -
Как я уже сказал, всё состояние приложения хранится в единственном месте. Это место называется «стор». Например, у такого приложения, которое показывает цифру,.. -
...стор может быть таким. -
Второй принцип — состояние приложения нельзя изменять напрямую. -
Состояние нельзя изменять напрямую. То есть вот так: store.number += 1
— делать нельзя. Чтобы изменить стор, нужно сгенерировать событие с описанием изменения. Это делается с помощьюstore.dispatch()
. Объект, который передаётся вdispatch()
, называется «экшен» (action). Это обычный JavaScript-объект. Он может содержать любые поля, которые будут описывать изменение, единственное обязательное поле —type
. Редакс запрещает напрямую изменять состояние, потому что нужно следить за изменениями, чтобы при изменениях стора обновлять представление. -
Третий принцип — все изменения обрабатываются функцией, которая называется «редьюсер» и которая должна быть чистой. Ещё раз: экшен — это событие, которое генерируется функцией dispatch()
. -
Чистая функция — это функция, которая всегда для одного и того же входного значения возвращает одинаковый результат + не изменяет ничего снаружи себя. Синус — чистая функция, потому что для одного и того же угла она всегда возвращает одно и то же значение. parseInt()
— чистая.$.ajax()
— не чистая, потому что в разные моменты времени она может вернуть разные значения. -
Первая функция — чистая, потому что она ничего не изменяет снаружи. Вторая — нет, потому что она изменяет свой параметр, и внешний код после вызова этой функции работает уже с другим параметром. Третья — нет, потому что она тоже изменяет состояние системы снаружи себя (выводит что-то в консоль). -
Эта функция, которая обрабатывает экшены, называется редьюсером. Она обрабатывает каждый экшен, который происходит в приложении. Функция принимает на вход есть текущее состояние приложения и экшен, который только что произошёл, и для каждого типа экшена изменяет состояние таким образом, как считает нужным. Обратите внимание, что состояние здесь не изменяется напрямую. Вместо этого возвращается новое состояние, которым Редакс уже сам заменяет старое. -
Итак, это главные принципы редакса... (Список принципов: http://redux.js.org/docs/introduction/ThreePrinciples.html) -
...и согласно ним редакс-приложение строится по такой схеме. Вопрос: что делать с сайд-эффектами? -
Сайд-эффекты — это когда вы изменяете глобальную переменную, делаете запрос на сервер, пишете что-то в лог. -
Проблема в том, что в этой схеме сайд-эффектам нет места. Единственное место, где происходят изменения — это редьюсер, а он обязан быть чистой функцией. -
На помощь приходит middleware. Мидлвейр находится между кодом, который диспатчит экшены (то есть генерирует события) и редьюсером (то есть функцией, которая их преобразует в новое состояние), пропускает через себя все возникающие экшены, меняет их (если считает нужным) и отправляет дальше — или не отправляет. Особенность мидлвейра в том, что он быть чистой функцией не обязан — и поэтому он может делать сайд-эффекты вроде запросов на сервер или чего угодно. -
На самом деле мидлвейров может быть много, и тогда они организуются по цепочке. -
Фактически, мидлвейр — это просто функция, которая принимает текущий стор, следующий в цепочке мидлвейр и текущий экшен и что-то делает с ним. Мидлвейры подключаются при создании стора. createStore
иapplyMiddleware
— это методы редакса. Я не буду рассказывать, как писать свой мидлвейр, потому что создавать свой миддлвейр для работы с сайд-эффектами — слишком сложно. Вместо этого мы посмотрим на те решения, которые уже есть. К слову, мидлвейры создаются сообществом и не входят в поставку редакса. -
Есть куча методов организации сайд-эффектов. Я знаю два. Первый — это redux-thunk, с которым вы, создавая приложение с редаксом, столкнётесь точно. Зачем он нужен? Классический редакс умеет работать только с экшенами, которые представлены простыми объектами. -
Применяя мидлвейр redux-thunk вот таким вот образом,.. -
...вы получаете возможность вместо простых обьектов... -
…диспатчить функции. Когда вы задиспатчите функцию, redux-thunk вызовет её и передаст в неё метод dispatch стора, который вы потом сможете вызвать тогда, когда вам нужно. Такие функции называются thunk-ами. -
Или вот так, если вынести создание экшена в отдельную функцию. Такое вынесение, кстати, — стандартный шаблон в Редаксе, и функция, создающая экшен, называется экшен-криэйтор. -
Что ещё можно делать с redux-thunk? Можно диспатчить несколько экшенов подряд, чтобы выполнить логаут и затем очистить стор. -
Можно диспатчить экшены по таймеру, чтобы побесить пользователя всплывающими модальными окнами. Можно делать что угодно, потому что у нас есть dispatch
— и даже есть текущее состояние стора, которое передаётся вторым параметром. Redux-thunk удобен для простых сайд-эффектов. Когда нужно организовать сложный поток сайд-эффектов, он становится неудобен. В этом случае пригождается redux-saga. -
Что такое redux-saga? Вообще, сага — это подход, пришедший с бекенда. Redux-saga — это альтернативный подход к организации сайд-эффектов. Вместо того, чтобы диспатчить функции, которые обрабатываются redux-thunk-ом, вы создаёте сагу, которая собирает всю логику обработки внутрь себя. В отличие от thunk-ов, которые выполняются, когда вы их диспатчите, саги запускаются при старте приложения и как бы «работают в фоне». Саги слушают все экшены, которые диспатчит стор, и решают, что делать с ними. И у саг в редаксе два преимущества по сравнению с thunk-ами: — Они позволяют организовывать сложные последовательности сайд-эффектов — И они очень легко тестируются Давайте посмотрим, как выглядит сага. -
Помните старый пример логина с помощью thunk-а? Давайте поменяем его на сагу. -
Что меняется? — Функция становится генератором — Прямые вызовы заменяются на непрямые, через call()
;dispatch()
заменяется наput()
— Стиль вызовов меняется на синхронный (без колбеков) — и именно вот это позволяет писать сложные асинхронные запросы -
На экшены можно подписываться двумя способами. Первый — простой: мы оборачиваем сагу и просим redux-saga вызывать её каждый раз, когда произойдёт экшен LOGIN_REQUESTED
. -
Второй способ более сложный. Мы помещаем код саги в бесконечный цикл и размещаем в начале yield take('LOGIN_REQUESTED')
. Когда сага начнёт выполняться, выполнение дойдёт до этой строки, остановится и не продолжится до тех пор, пока не сгенерируется экшенLOGIN_REQUESTED
. -
Второй способ позволяет ловить несколько экшенов. Здесь код на месте многоточия выполнится только тогда, когда произойдёт ANOTHER_ACTION
, которому предшествовалLOGIN_REQUESTED
. -
Так саги устанавливаются. Они запускаются сразу после вызова createStore()
. Примеры кода с сагами -
-
Бизнес-логика -
С бизнес-логикой всё просто. -
Бизнес-логику — мидлвейрам! Почему? — Сайд-эффекты, которым принадлежит большая часть бизнес-логики (thunk-ах или сагах), и так будут в мидлвейрах. Есть смысл перенести оставшуюся бизнеслогику туда — Мидлвейры имеют доступ ко всему стору, а редьюсеры — только к части — Можно диспатчить несколько экшенов Ещё ссылки: Question: Where to put business logic / validations? Recommendations for best practices regarding action-creators, reducers, and selectors -
То есть на этой схеме бизнес-логика должна находиться в этом чёрном блоке. -
Пример кода из одного из лучших open-source проектов на редаксе. Редьюсеры в нём максимально простые, … -
...а экшен-криэйторы сложные и содержат всю логику. -
Тестирование -
Что вообще можно тестировать в Редаксе? Обычные экшен-крийэторы тестировать просто. Асинхронные — сложно, потому что они возвращают функции, а проверить, что вернулась правильная функция, сложно. Редьюсеры — просто. (Как тестировать: экшен-криэйторы, асинхронные экшен-криэйторы, редьюсеры.) -
Я расскажу про то, как тестировать саги. Саги тестировать очень просто. -
Помните этот слайд? Самый кайф — вот в этих выделенных функциях. -
call($.ajax)
не вызывает$.ajax
напрямую, а генерирует декларативное описание вызова в виде простого JS-объекта. Благодаря этому для тестирования достаточно сравнить два объекта черезdeepEqual
. -
Вот такую вот сагу… -
…протестировать очень просто. Обратите внимание, что для того, чтобы сэмулировать ответ сервера, мы просто передаём его в генератор. И проверить то, что генератор вызывает $.ajax
и делает этим запрос на сервер, очень просто, потому что генератор возвращает объект с описанием вызова. -
То есть ещё раз: мы декларируем вызов AJAX-запроса… -
…и просто сравниваем декларацию. Точно так же и с генерацией экшенов. Если мы хотим проверить, что сага сгенерирует экшен LOGIN_SUCCESS
, мы просто сравниваем ту декларацию, которую вернул генератор, с ожидаемой. -
Что ещё не поместилось в доклад Ducks redux-devtools redux-devtools-extension -
Reselect Документация -
andrewngu/sound-redux Hashnode/mern-starter -
Будьте умничками. -
Спасибо! twitter.com/iamakulov