Что такое Redux?
-
Разработан в Facebook 2013
-
Библиотека (а не фреймворк) для управления state. Реализует Flux-архитектуру.
-
Её надо инсталлировать отдельно (не идёт в комплекте с React b т.д.)
-
Позволяет создавать свой store и удобно работать с ним
-
В частности, уменьшает связность - позволяет передавать данные не по цепочке props, а сразу в нужную компоненту
-
Ссылки
Популярные библиотеки
- Есть несколько библиотек, которые хорошо дополняют Redux:
React-Redux
— см. ниже.Immutable.js
— немутабельные структуры данных для JavaScript! Используйте их для хранения состояния, чтобы быть уверенным, что оно не меняется там, где не должно, а также чтобы сохранить функциональную чистоту редьюсеровredux-thunk
— используется когда нужно, чтобы действия (actions) имели какой-либо побочный эффект в дополнение к обновлению состояния приложения. Например, вызов REST API, или установка маршрутов (routes), или даже вызов других действий.reselect
— используется для создания составных, лениво исполняемых отображений. Например для конкретного компонента вам может потребоваться:- вставить только определенную часть глобального состояния, а не полностью
- вставить дополнительные производные данные, например "итого" или "результаты валидации данных", не сохраняя все это в состоянии
Недостатки Redux
- Высокая «связность»
- «Связность» (coupling) — взаимная зависимость модулей между собой. Сколько изменений надо внести в модули при изменении другого модуля. Чем ниже этот показатель — тем лучше.
- Нарушение принципа "Low coupling, high cohesion" (Низкая связность, высокая сцепленность)
- Управление состоянием, которое должно быть цельным внутри компонента, оказывается размазанным по множеству файлов и сущностей.
- Связи, которые должны оставаться внутри, выходят наружу.
- состояние всех компонент хранится в одном месте (глоальный стор), сложно перенести модуль в другой проект, надо тянуть за собой структуру Redux.
- Альтернативы
- реализовать упарвление состоянием через хуки React (useReducer() и т.д.)
- концепции «изолированных модулей»,
- «слои»
- Много «кода ради кода» (boilerplate кода)
- Даже небольшое изменение функционала может потребовать относительно большие изменения в коде,
- так еще и код этот однотипный и не несущий никакой полезной для данной конкретной задачи нагрузки.
- Инструменты, направленные на решение этой проблемы, существуют — Redux Toolkit
- Необходимость выбирать дополнительный слой
- redux-thunk, redux-saga, redux-observable...
- Порог вхождения выше
- если человек уже умеет работать с функциональными / классовыми компонентами и понимает что такое методы жизненного цикла и «состояния компонента» — ему не так сложно обучиться управлением state на хуках. А понимание Flux-архитекутры (например Redux) требует некоторых усилий.
- Альтернативные точки зрения
- Шаблоны и архитектуры не имеют плюсов и минусов сами по себе. Плюсы и минусы у них есть только в сравнении с другой архитектурой / шаблоном.
- Redux - просто библиотека. Важно её корректно использовать.
- Redux уменьшает количество шаблонного кода по сравнению со старым «шаблоном Flux»: http://redux.js.org/usage/reducing-boilerplate
- Люди используют Redux, потому что хотят, чтобы поток данных через их JS-код был согласованным, предсказуемым и понятным по сравнению со кастомными JS-скриптами, которые не соответствуют какой-либо общей общей архитектуре или шаблонам программирования. Если вам не нужны эти преимущества, возможно, вам лучше написать кастомный JS.
- Ещё
- Редакс не умеет идиоматически описывать события — когда у вас что-то произошло, и вам просто надо сайдэффекты. В терминах редакса это всегда превращается в монстров, когда пишут в стейт какие-нибудь пустые объекты (для change detection), и запускают рендер компонентов, когда он по факту не нужен (редакс не умеет просто сообщить в реакт о факте изменения стейта, это обязательно сопровождается рендером).
- Из-за этого всего неаккуратная попытка создания в редаксе сайдэффектов на какие-нибудь частые события (scroll, мыша) может очень легко отожрать весь процессор и привести к тормозам на ровном месте. При этом весь код будет написан "по заветам" официальной документации.
- принципиально нет человеческого способа доставить обновление стейта в компонент реакта без рендера. Способ придумать можно, но будет несколько… нечеловечески.
- в компоненте есть ref на какой-то внутренний элемент, и надо с этим элементом что-то делать при некоторых изменениях редуксового стора, но без перерендера самого компонента?
- У меня у самого есть вопросы к реализации вычислимых полей через селекторы, равно как и использование чистых функций в языке без даже намека на ссылочную прозрачность. Но конкретно для вашего примера сразу несколько вариантов приходит в голову
- Просто импортировать стор в компонент вручную и вручную привязаться к стору через subscribe внутри компонента, один раз при создании компонента.
- Написать свою обертку вокруг useSelector, которая будет принимать на вход селектор и колбек, который нужно дернуть при изменении результата селектора. Внутри обертки дергаем useSelector со вторым параметром (prev, next) => {if(prev !== next) {setTimeout(()=>callback(next))};return true}
- Тупо мутировать стейт, но тогда не понятно как передавать информацию об изменении, и почему просто не использовать отдельный синглтон
- Первые два способа может и не совсем стандартные, но вроде должны работать.
- Ссылки
Альтернативы Redux
- Хуки, в частности: useState, useReducer, useContext, пользовательские хуки. Ну и ContexAPI
- MobX
- React Query - библиотека для получения, кеширования, синхронизации и обновления состояния React-приложениях, хранящегося на сервере
- SWR — альтернатива React Query
- Recoil — новая библиотека для управления состоянием от Facebook
- Overmind
Базовые понятия (кратко)
Store
(хранилище) — объект, содержит объектstate
и методы для работы с ним.State
(состояние) — объект хранящий актуальное состояние системы.- Методы = как его менять, как получить актуальное состояние хранилища (
getstate
), как подписаться на изменения (subscriber
)... Их может быть много.
Dispatch
(отправка) — один из методовstore
. Объединяет все методы для правкиstate
.- Хотим произвести любые изменения в
state
- вызываем методdispatch
- Хотим произвести любые изменения в
Actions
— объекты которые мы из UI (React) передаем в методdispatch()
.- Единственный способ внесения правок в
state
— вызыватьdispatch
, внутри которого некийaction
- Единственный способ внесения правок в
Type
иPayload
— параметры объектаaction
type
— строка, имя метода который будет менятьstate
. По нему Redux понимает — что именно мы хотим сделать сstate
payload
— данные, которые нужны для измененийstate
. Их может и не быть — например просто поменяли состояние системы на "Жду данных с сервера".
ActionCreators()
— функции, создают объектAction
. Принимают данные-payload нужные для правкиstate
, и возвращают объектaction
(с нужным type и payload).- Передавать
action
напрямую вdispatch()
— плохой тон, код грязный и можно ошибиться.ActionCreators()
- Мы диспатчим не
ActionCreator
- диспатчится его вызов. Т.е. запустится функцияdispatch()
, выполнитActionCreator
, и уже потом выполнитсяdispatch()
с переданными в него результатами работыActionCreator
(т.е. с переданным объектомAction
)
- Передавать
Reducers()
— функции внутриdispatch()
. Отвечают за правку опр. частиstate
. Принимаютaction
иstate
, возвращают новыйstate
- Принимают все
actions
входящие вdispatch()
, и какой-то отдельный кусокstate
(например, отвечает за отдельную страницу). - Внутри стоит конструкция switch...case. Если
type
объектаaction
описан в этом switch - применяются изменения. Иначе - просто игнорируются - Нужны чтоб упростить работу со
state
— проще работать с каким-то небольшим объектом (например, описывающим состояние отдельной страницы). Для этого большойstate
дробят на части при помощиreducers()
- Внутри
reducer
— набор методов для изменения данной частиstate
. Reducer'у
делегировано преобразование ветокstate
. Та самая "простынь" с кучейswitch
/case
. Туда же пихаютactionCreator
.
- Принимают все
Thunk()
— функция, делает какой-то асинхронный код и умеетdispatch(actions)
. Нужна для асинхронных запросов.- Вызывается из UI (React), как обычный
dispatch()
- Внутри себя выполняет асинхронный код и диспатчит обычные
actions
. - React Thunk — отдельная библиотека, уже включена в Redux Toolkit. Добавляется в Redux и позволяет использовать асинхронный код внутри
dispatch()
. - В программировании
thunk
— это подпрограмма, используемая для ввода вычисления в другую подпрограмму. В основном используются для задержки вычисления до тех пор, пока не потребуется его результат, или для вставки операций в начале или конце другой подпрограммы. - Термин
thunk
возник как причудливая форма глагола думать (разговорная форма прошедшего времени think.). Это относится к первоначальному использованию thunks в компиляторах ALGOL 60, что требовало специального анализа (размышления), чтобы определить, какой тип подпрограммы генерировать. - Hexlet - Асинхронные запросы (Thunk). React: Redux Toolkit
- Вызывается из UI (React), как обычный
ThunkCreator()
— функция-обёрткаthunk()
. Нужна чтоб передать вthunk()
данные-payload для правкиstate
.Thunk
берёт данные из замыкания, образуемогоThunkCreator()
. https://youtu.be/eWdnjfRu9Io?t=1087- Как и с
ActionCreator
, мы диспатчим неThunkCreator
- диспатчится его вызов. Т.е. запустится функцияdispatch()
, выполнитThunkCreator
, и уже потом выполнитсяdispatch()
с переданными в него результатами работыThunkCreator
(т.е. с переданнымThunk
который через замыкание получил нужные данные)
Saga()
— альтернативаthunk
. Тоже библиотека. Сложнее, более продвинутая- https://habr.com/ru/post/351168/
- Redux-saga — библиотека. Нацеленная делать сайд-эффекты проще и лучше путем работы с сагами.
- Саги — дизайн паттерн проектирования. Пришел из мира распределенных транзакций, где сага управляет процессами, которые необходимо выполнять транзакционным способом, сохраняя состояние выполнения и компенсируя неудачные процессы.
- Узнать больше
- Кузебюрдин (IT-Kamasutra) про Саги
- посмотреть Применения паттерна Сага от Caitie McCaffrey,
- статья, которая первая описывает саги в отношении распределенных систем (если вы амбициозны)
- Альтернативы redux-saga. Две самых популярных:
- redux-observable (базируется на RxJS)
- redux-logic (базируется на RxJS наблюдателях, но даёт свободу писать логику в других стилях).
Middleware()
— функция-обёрткаdispatch()
. Нужна чтоб выполнить асинхронный код между отправкой из UI иdispatch()
- В
store
приходит что-то (action
илиthunk
). Это что-то не сразу попадает вstore
, а вначале обрабатывает функцией-обёрткой вокругDispatch
—Middleware
. Если этоAction
-Middlewear
сразу пропустит его вstore
. Но, если этоthunk
—middlewear
вначале выполнит его, дождётся ответа (если код был асинхронный), получит ответ и снова проверит — пришёлaction
, или ещё одниthunk
(так тоже бывает). И так пока не придётaction
. - Зачем это? Проблема в том, что
store
умеет работать только сactions
(объектами). Если в него попадётthunk
( функция, да ещё с асинхронными методами) — он ничего сделать не сможет. Поэтому, надо всю эту асинхронную логику где-то выполнить — между отправкой из UI и приходом в методstore
. Для этого и сделали обёртку вокругstore
. - Выполнять асинхронные запросы внутри
reducer
нельзя потому чтоreducer
должен быть чистой функциейreducer
должен отдавать новыйstate
мгновенно, т.е. никаких ожиданий завершения асинхронного запроса
- Redux предоставляет нам такую штуку как middleware, которая стоит между диспатчом экшена и редюсером.
- Существует две самые популярные middleware библиотеки для асинхронных экшенов в Redux, это — Redux Thunk и Redux Saga..
- Middleware компонуемы — несколько мидлваров можно объединить вместе, где каждый мидлвар не должен знать, что происходит до или после него в цепочке.
- Подробнее
- В
Selectors
, библиотека Reselect- Итого
state
— объект хранящий актуальное состояниеstore
— объект-хранилище всего что связано с состояниемsubscriber
— метод для подписки на изменения в стэйтеdispatch
— метод, для изменения стэйта. Все манипуляции со стэйтом делаем через dispatchaction
— объект, который мы снаружи отправляем в метод dispatch. Содержит тип (какое изменение произвести) и, если надо, данныеreducer
— функция, которая получает отдельный кусок стэйта и action. Если нужно - применяет этот экшен к стейту и возвращает обновлённый кусок стэйта (потом из кусков собирается новый стэйт)
Библиотека React-Redux
-
Отдельная библиотека, выступает как прослойка между React и Redux.
-
Позволяет работать с Redux не заморачиваясь кучей сложностей.
-
Инкапсулирует часть вещей, прячет от нас всякие детали связанные с контекстом, store, dispatch, subscribe...
-
Обладает очень простым интерфейсом.
-
Самое интересное:
<Provider store>
— можно создать обёртку для React-приложения и делать состояние Redux доступным для всех компонентов-контейнеров в его иерархии.connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
— позволяет создавать компоненты высшего порядка. Это нужно для создания компонентов-контейнеров на основе базовых компонентов React.
-
Ссылки
Библиотека Redux Toolkit
-
Библиотека от разработчиков Redux.
-
Релиз — 2019.
-
Аналог «Create React App» для React — можно работать и без неё, но с ней намного удобнее. До релиза библиотека называлась «redux-starter-kit»
-
Зачем:
- помогает быстро начать использовать Redux;
- упрощает работу с типичными задачами и кодом Redux;
- позволяет использовать лучшие практики Redux по умолчанию;
- предлагает решения, которые уменьшают недоверие к бойлерплейтам.
-
Наиболее значимые функции:
configureStore
— функция, предназначенная упростить процесс создания и настройки хранилища (store
);- Автоматически добавляет
redux-thunk
в store - Расширение
Redux DevTools
уже включено
- Автоматически добавляет
createSlice
— объединяет в себе функционал createAction и createReducer;- теперь написание логики Redux сводится к конфигурации. Дайте ему имя, initialState и логику ваших редьюсеров, и Redux Toolkit уже предоставит вам:
- автоматически генерирует action creators & action types.
- кажется, заменяет константы
- Эти редукторы передаются в createReducer()который обеспечивает неизменную логику для обновления состояния.
- теперь написание логики Redux сводится к конфигурации. Дайте ему имя, initialState и логику ваших редьюсеров, и Redux Toolkit уже предоставит вам:
createReducer
— функция, помогающая лаконично и понятно описать и создать редьюсер;- заменяет
switch...case
?
- заменяет
createAction
— возвращает функцию создателя действия для заданной строки типа действия;- возвращает action creator, которого можно экспортировать, а затем диспатчить
- Когда вы вызываете action creator с аргументами, он автоматически становится объектом action.payload .
- Строка, которую вы передаете в createAction, становится типом действия, которое вы затем можете использовать в своем редюсере.
createAsyncThunk
— похоже на createAction, но для случая отправки асинхронных действий- возвращает thunk action creator
- Первый параметр — это тип действия Redux, но в этом случае строка не генерирует никаких функций редуктора автоматически, поскольку не знает деталей вашей реализации.
- Второй параметр — это обратный вызов, в котором вы реализуете асинхронное поведение (например, выполнение вызовов ajax) и должны возвращать promise.
createSelector
— функция из библиотеки Reselect, переэкспортированная для простоты использования.createEntityAdapter
— нормализовать объекты данных- создает удобную структуру для каждой коллекции. Очень похоже на то, что предоставляет библиотека normalizr
- Предоставляет множество функций CRUD для обновления вашей коллекции в ваших редьюсерах.
- Содержит функцию getSelectors() , которая предоставляет набор очень полезных селекторов (selectIds, selectEntities, selectAll, selectTotal, selectById), которые вы можете экспортировать.
-
Ссылки
State (состояние)
-
Общее
- Специальный js-объект <внутри компонента>. Хранит данные, которые могут изменятся с течением времени.
- Это инструмент, позволяющий обновлять пользовательский интерфейс, основываясь на событиях.
- Задачи компоненты - отрисовывать какие-то данные. Эти данные всегда называются state (состояние приложения).
- Узнать состояние компонента можно с помощью конструкции this.state.
- Изменить состояние можно с помощью this.setState(), если передадим этой функции объект, представляющий новое состояние.
-
State-management — управление данными
- Задумывая архитектуру нового приложения, первым делом всегда думать - как я собираюсь организовать state-managment (управление данными)?
- Чаще всего выбор зависит от того, в чём больше опыта.
- State (Business Logic Layer) важнее чем UI.
- Какие есть подходы к state-management?
- local state of class component - локальный state классовых компонент. Используется не всегда. Для простых, небольших задач, маленьких проектов.
- Redux (одна из реализаций FLUX) - функциональное программирование
- MobX - ООП
- и ещё много других
-
Не изменяйте state напрямую
- // Неправильно
this.state.comment = 'Привет';
- Вместо этого используйте setState():
- // Правильно
this.setState({comment: 'Привет'});
- Конструктор — это единственное место, где вы можете присвоить значение this.state напрямую.
-
Обновления state могут быть асинхронными
-
React может сгруппировать несколько вызовов setState() в одно обновление для улучшения производительности.
-
Поскольку this.props и this.state могут обновляться асинхронно, вы не должны полагаться на их текущее значение для вычисления следующего состояния.
-
Например, следующий код может не обновить счётчик:
-
// Неправильно
-
this.setState({ counter: this.state.counter + this.props.increment, });
-
Правильно будет использовать второй вариант вызова setState(), который принимает функцию, а не объект. Эта функция получит предыдущее состояние в качестве первого аргумента и значения пропсов непосредственно во время обновления в качестве второго аргумента:
-
// Правильно
-
this.setState((state, props) => ({ counter: state.counter + props.increment }));
-
-
Однонаправленный поток данных
- В иерархии компонентов, ни родительский, ни дочерние компоненты не знают, задано ли состояние другого компонента.
- Также не важно, как был создан определённый компонент — с помощью функции или класса.
- Состояние часто называют «локальным», «внутренним» или инкапсулированным. Оно доступно только для самого компонента и скрыто от других.
- Компонент может передать своё состояние вниз по дереву в виде пропсов дочерних компонентов:
<h2>Сейчас {this.state.date.toLocaleTimeString()}.</h2>
- Своё состояние можно передать и другому пользовательскому компоненту:
<FormattedDate date={this.state.date} />
- Компонент FormattedDate получает date через пропсы, но он не знает, откуда они взялись изначально — из состояния Clock, пропсов Clock или просто JavaScript-выражения:
-
function FormattedDate(props) { return <h2>Сейчас {props.date.toLocaleTimeString()}.</h2>; }
- Этот процесс называется «нисходящим» («top-down») или «однонаправленным» («unidirectional») потоком данных. Состояние всегда принадлежит определённому компоненту, а любые производные этого состояния могут влиять только на компоненты, находящиеся «ниже» в дереве компонентов.
- Если представить иерархию компонентов как водопад пропсов, то состояние каждого компонента похоже на дополнительный источник, который сливается с водопадом в произвольной точке, но также течёт вниз.
Store (хранилище)
- ООП-объект, который управляет state (объект хранящий состояние приложения)
- Там лежит:
- сам state
- методы для работы с ним
- Создаётся при помощи метода(?) createStore(reducers)
Dispath (отправка)
- англ. "отправка"
- Метод объекта store, который предназначен для вызова всех методов, изменяющих store
- Т.е. вместо того чтобы прокидывать кучу разных методов, мы отдаём один единственный - disptach.
- В него передаём объект action, у которого указан type и есть нужные данные. Dispatch, на основе этого type и данных, вносит правки в store (прежде всего - в state)
- Метод объекта store, через который вызываем все другие методы объекта Store (что изменить state, т.е. состояние приложения)
- Мы вводим в наш объект store один единственный метод, через который будем вызывать все другие методы объекта.
- Он принимает некий объект action. Выглядит так: dispath(action).
- У action обязательно должно быть текстовое свойство type='' - в нём передаётся название требуемого действия (т.е. метода).
- Эти текстовые названия всегда пишутся заглавными.
- State всегда меняется через dispath(action)
- Ссылки
Action
- Объект, который через метод dispath передаётся в наш объект store, и там производит некие действия с данными (state)
- У action есть как минимум одно свойство,
type
. - По
type
dispath
определяет, какие именно действия надо произвести со state (какую ветку действий выбрать) - Вопрос: Если в компоненте один коллбэк вызывает последовательно несколько actions один за другим - они выполнятся в том же порядке?
- Ответ: «да». Redux store не возьмёт в работу второй action, пока не выполнится первый. Иначе бы Redux не мог нормально управлять state.
Action Creator
- Вспомогательная функция, которая создаёт нужный объект-action.
- Функции, которые возвращают объект action. То, что передаётся в mapDispatchToProps
- В компоненте хотим вызвать dispatch с каким-то экшеном, чтоб изменить стэйт. Для этого заранее создали в редьюсере экшен-криэйтор для данного экшена. Этот экшен-криэйтор импортировали в компоненту. В компонененте вызвали dispatch(экшен_криэйтор(payload)).
- Зачем надо? Сложно сказать. По идее. у action может быть сложная структура объекта. Чтоб каждый раз её заново не писать - сделали такую штуку.
- Содержит action - type и список данных, которые может получать.
- Пример:
-
export const updateTaskStatus = (status, id) => ({ type: UPDATE_TASK_STATUS, newStatus: status, taskId: id, });
-
Reducer (уменьшатель)
- Чистая функция, принимает state и action. Та самая простынь, где много switch
- Применяет action к этому state (если нужно) и возвращает новый state (если не изменился - тот же).
- Позволяет разделить метод dispath на отдельные куски, чтоб с ним было удобнее работать.
- Обычно каждый reducer отвечает за какую-то ветку state - например этот работает с одной страницей, а тот с другой; или один работает с цитатами, а другой с пользователями.
- Reducers - это отдельные функции, а не методы объекта store. Они лежат отдельно от store. Поэтому store (и его метод dispatch) не в курсе, какой action какому reducer нужен - мы отправляем любой входящий action всем имеющимся reducers. Для работы reducer ему кроме action нужен ещё и state. Но, мы не отправляем весь state целиком каждому reducer - нет, каждому мы отправляем только ту ветку, с которой он работает.
- Reducer - только преобразователь. Он не вызывает subscriber и другие callbacks.
- Если тип action неизвестен - выдаём изначальный state
-
const task_tables_reducer = (state = initialState, action) => { switch (action.type) { case IS_PAGINATION: { return {...state}; } default: return state; } }
- Смотри также в разделе «React — Компоненты. Компоненты = чистые функции»
Connect. MapStateToProps. MapDispatchToProps
-
Общее
- API-функция предоставляемая пакетом react-redux.
- Позволяет создавать контейнерные компоненты
- Пробрасывает в презентационную компоненту данные из store, в виде props. Благодаря mapStateToProps
- Подписывает презентационную компоненту на все изменения state, которые мы объявили в mapStateToProps
- Позволяет контейнерной компоненте изменять store, благодаря mapDispatchToProps
- Connect автоматически делает подключенные компоненты «чистыми», то есть они будут повторно рендериться только при изменении их props — тоесть, когда изменяется их срез состояния Redux. Это предотвращает ненужный ре-рендер и ускоряет работу приложения.
- Connect() используется для создания компонентов-контейнеров, которые подключены к хранилищу Redux. Хранилище, к которому осуществляется подключение, получают от самого верхнего предка компонента с использованием механизма контекста React.
- Если вам, в React-компоненте, нужно получать данные из хранилища, или требуется диспетчеризовать действия, или нужно делать и то и другое, вы можете преобразовать обычный компонент в компонент-контейнер, обернув его в компонент высшего порядка, возвращаемый функцией connect() из react-redux.
- Вы можете создать компонент-контейнер самостоятельно и вручную подписать компонент на хранилище Redux, используя команду store.subscribe(). Однако использование функции connect() означает применение некоторых улучшений и оптимизаций производительности, которые, вы, возможно, не сможете задействовать при использовании других механизмов.
- Функция connect(), кроме того, даёт разработчику дополнительную гибкость, позволяя настраивать компоненты-контейнеры на получение динамических свойств, основываясь на свойствах, первоначально им переданных. Это оказывается очень кстати для получения выборок из состояния, основываясь на свойствах, или для привязки генераторов действий к конкретной переменной из свойств.
- Если ваше React-приложение использует несколько хранилищ Redux, то connect() позволяет легко указывать конкретное хранилище, к которому должен быть подключён компонент-контейнер.
- Прежде чем преобразовывать обычный компонент React в компонент-контейнер с использованием connect(), нужно создать хранилище Redux, к которому будет подключён этот компонент.
- Функция connect(), предоставляемая пакетом react-redux, может принимать до четырёх аргументов, каждый из которых является необязательным. После вызова функции connect() возвращается компонент высшего порядка, который можно использовать для оборачивания любого компонента React.
-
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
-
Connect
-
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
-
connect()
— это API React-Redux. используется для создания компонентов-контейнеров, которые подключены к хранилищу Redux. -
Если вам, в React-компоненте, нужно получать данные из хранилища, или требуется диспетчеризовать действия, или нужно делать и то и другое, вы можете преобразовать обычный компонент в компонент-контейнер, обернув его в компонент высшего порядка, возвращаемый функцией connect() из react-redux.
-
Если ваше React-приложение использует несколько хранилищ Redux, то connect() позволяет легко указывать конкретное хранилище, к которому должен быть подключён компонент-контейнер.
-
Ссылки
-
-
mergeProps
- Если функции connect() передаётся аргумент mergeProps, то он представляет собой функцию, которая принимает следующие три параметра:
- stateProps — объект свойств, возвращённый из вызова mapStateToProps().
- dispatchProps — объект свойств с генераторами действий из mapDispatchToProps().
- ownProps — исходные свойства, полученные компонентом.
- Эта функция возвращает простой объект со свойствами, который будет передан заключённому в обёртку компоненту. Это полезно для осуществления условного маппинга части состояния хранилища Redux или генераторов действий на основе свойств.
- Если connect() не передают эту функцию, то используется её стандартная реализация:
-
const mergeProps = (stateProps, dispatchProps, ownProps) => { return Object.assign({}, ownProps, stateProps, dispatchProps) }
- Если функции connect() передаётся аргумент mergeProps, то он представляет собой функцию, которая принимает следующие три параметра:
-
options
- Объект с параметрами. Необязательный объект, передаваемый функции connect() в качестве четвёртого аргумента.
- Содержит параметры, предназначенные для изменения поведения этой функции. Так, connect() представляет собой специальную реализации функции connectAdvanced(), она принимает большинство параметров, доступных connectAdvanced(), а также некоторые дополнительные параметры.
- Страница документации - какие параметры можно использовать с connect(), и как они модифицируют поведение этой функции.
mapStateToProps
-
Общее
- Функция, возвращает либо обычный объект, либо другую функцию.
- Передача mapStateToProps в качестве аргумента для функции connect() приводит к подписке компонента-контейнера на обновления Redux Store. mapStateToProps будет вызываться каждый раз, когда состояние Store изменяется. Если слежение за обновлениями состояния не нужно - передайте connect() в качестве значения этого аргумента undefined или null.
-
Параметры
- mapStateToProps() объявляется с двумя параметрами, второй из которых является необязательным:
- Первый параметр представляет собой текущее состояние хранилища Redux.
- Второй параметр (ownProps), если его передают, представляет собой объект свойств, переданных компоненту
-
const mapStateToProps = (state, ownProps) => ({ coin: coinSelector(state, ownProps), isLoading: isCoinsLoadingSelector(state), });
- mapStateToProps() объявляется с двумя параметрами, второй из которых является необязательным:
-
Параметр ownProps
- Это свойства компонента.
- Функции
mapStateToProps
иmapDispatchToProps
, переданныеconnect()
, могут быть объявлены со вторым параметромownProps
, представляющим собой свойства компонента. - Но! Если число обязательных параметров объявленной функции
mapStateToProps
меньше, чем 2, тогдаownProps
передаваться не будет. - Если же функция объявлена с отсутствием обязательных параметров или, как минимум, с 2 параметрами,
ownProps
будет передаваться.
-
Что возвращает mapStateToProps
- Если из mapStateToProps будет возвращён обычный объект, то возвращённый объект stateProps объединяется со свойствами компонента.
- Если же mapStateToProps возвращает функцию, то эта функция используется как mapStateToProps для каждого экземпляра компонента.
- Это может пригодиться для улучшения производительности рендеринга и для мемоизации.
mapDispatchToProps
-
Объект, содержащий набор actionCreators.
-
Может быть либо объектом, либо функцией, которая возвращает обычный объект или другую функцию.
-
Используется в connect
-
Позволяет контейнерной компоненте диспатчить изменения в store
-
Если в качестве аргумента mapDispatchToProps используется объект, то каждая функция в объекте будет воспринята в качестве генератора действий Redux и обёрнута в вызов метода хранилища dispatch(), что позволит вызывать его напрямую. Получившийся в результате объект с генераторами действий, dispatchProps, будет объединён со свойствами компонента.
-
При использовании в качестве аргумента mapDispatchToProps функции программист должен самостоятельно позаботиться о возврате объекта dispatchProps, который осуществляет привязку генераторов действий с использованием метода хранилища dispatch(). Эта функция принимает, в качестве первого параметра, метод хранилища dispatch(). Как и в случае с mapStateToProps, функция также может принимать необязательный второй параметр ownProps, который описывает маппинг с исходными свойствами, переданными компоненту.
-
Если эта функция возвращает другую функцию, то возвращённая функция используется в роли mapDispatchToProps, что может быть полезным для целей повышения производительности рендеринга и мемоизации.
-
Ссылки
Селекторы, библиотека reselect
-
Селектор
- Функция, принимает весь стэйт целиком, достаёт и обрабатывает какие-то данные и передаёт их в
mapStateToPros
(и дальше в UI). - Отдельный архитектурный слой, занимается получением, комбинированием и преобразованием данных. Например, данные из этого куска стэйта надо как-то обработать, объединить с данными из другого куска стэйта, отфильтровать, убрать дубли и только потом передать в React.
- Зачем нужны - чтобы разделить структуру данных в стэйте (BLL) и React (UI). Если мы поменяем структуру данных в стэйте, например переименуем какой-то объект, разделим его на несколько и т.д.- хорошо бы иметь одно место, где можно внести изменения. Иначе придётся прыгать по всем
mapStateToPros
приложения и менять структуру вручную. mapStateToPros
- предоставляется библиотекой React-Redux.
- Функция, принимает весь стэйт целиком, достаёт и обрабатывает какие-то данные и передаёт их в
-
Проблемы селекторов
- В Redux нельзя подписаться на изменение конкретного кусочка данных. Изначально, можно лишь узнать о том, что "где-то что-то изменилось".
- Т.к. 'mapStateToProps' вызывается в каждом компоненте при каждом изменении стэйта — каждый селектор тоже будет вызываться при каждом изменении стэйта. Даже если меняется совсем другая ветка. А если селектор плюс ко всему делает
.map()
,.filter()
,.reduce()
и т.д. — он ещё и создаёт новый объект (даже если данные в него пришли те же) => будет ненужный ре-рендер страницы. - Основные проблемы:
- Могут вызывать ненужные ре-рендеры страниц при каждом изменении в глобальном стэйте.
- Могут иметь сложную логику вычислений => будут создавать большую нагрузку, долго вычислять (вызываются при каждом изменении в глобальном стэйте).
- Трудны в отладке - debugger поставленный в селекторе вызывается при каждом изменении в глобальном стэйте, а не тогда когда меняется что-то в части стэйта с которой работает данный селектор.
- В Redux нельзя подписаться на изменение конкретного кусочка данных. Изначально, можно лишь узнать о том, что "где-то что-то изменилось".
-
Библиотека Reselect
- GitHub - Reselect.
- Библиотека для создания мемоизированных "селекторных" функций.
- Мемоизация = сохранение результатов выполнения функций для предотвращения повторных вычислений.
- Селектор по-прежнему вызывается каждый раз когда хоть что-то меняется в глобальном стэйте. Но результаты работы селектора сохранены, и при каждом новом вызове отдаётся этот - сохранённый результат.
- При этом объявлены «зависимости» - от каких частей стэйта зависит данный селектор. Если эти части меняются - результат работы селектора вычисляется и запоминается заново.
- Другими словами:
- мы возвращаем тот же массив, а не его копию - не будет ре-рендера
- мы не запускаем вычисления внутри селектора - не будет тратиться время и вычислительная мощность
- мы не запускаем вычисления внутри селектора - не попадаем в саму функцию, а значит debugger лишний рах не вызывается
- Не используйте reselect там, где не происходит вычислений.
- Не надо лишний раз засорять память, reselect трудно ускорить, и кеширование тут не поможет.
- Отдавая кеширование на откуп библиотеке, вы рискуете по неосторожности загадить память кешом. В браузере у вас один пользователь, да и страничка живет недолго, а на сервер приходит много пользователей, и процесс там живет долго.
-
Ссылки
- Habr — Готовим селекторы в Redux
- Habr — Несколько способов оптимизировать React-Redux приложение
- It-Kamasutra — React JS. Селекторы (reselect part 1)
- It-Kamasutra — React JS. MapStateToProps (reselect часть 2)
- It-Kamasutra — React JS. MapStateToProps (reselect часть 3)
- GitHub — Reselect
- Reselect — библиотека селекторов для Redux
Provider
-
Специальный компонент из пактеа «React-Redux».
-
Позволяет передавать store всем потомкам - теперь у connect() есть доступ к store
-
Оборачивается вокруг корневой компоненты
<App>
. -
Компонент принимает свойство store. Предполагается, что оно представляет собой ссылку на хранилище Redux, которое планируется использовать в приложении.
-
Свойство store передаётся, в соответствии с иерархией приложения, компонентам-контейнерам, с использованием механизма контекста React:
Compose
- Подход из функционального программирования
- Реализацию этой функции предоставляет, в частности, Redux
- Позволяет объединить n последовательных вызовов функций.
- Полезно в ситуации конвейера - мы передаём данные в функцию A, результат её работы передаётся в функцию B, результат работы B передаётся в C... и так далее
-
compose( connect(mapStateToProps, mapDispatchToProps), withAuthRedirect ) (component)
- Оборачивание идёт "снизу вверх" - вначале обернёт
withAuthRedirect()
, потомconnect(mapStateToProps, mapDispatchToProps)()
- Ссылки
Нормализация данных
-
Общее
Нормализация
— процесс удаления избыточных данных.- Для приведения структуры БД к виду, обеспечивающему минимальную логическую избыточность.
- Избыточность устраняется, как правило, за счёт декомпозиции отношений (таблиц), т.е. разбиения одной таблицы на несколько.
- Большинство приложений работают с данными, которые имеют вложенную структуру.
- Например, у постов в блоге есть автор и комментарии. У комментариев тоже есть авторы и могут быть лайки.
- Работать с такой структурой напрямую тяжело по нескольким причинам:
- Внутри неё дублируются данные, например, author. Из-за этого усложняется обновление
- Логика редьюсеров становится тем сложнее, чем больше вложенность
- Правильный подход при работе с Redux — воспринимать его как реляционную базу данных.
- Данные внутри хранилища должны быть нормализованы.
- При таком взгляде каждый слайс работающий с набором сущностей может восприниматься как отдельная таблица в базе данных.
Реляционная база данных
— информация хранится в таблицах, связанных друг с другом опр. отношениями.- Эти отношения позволяют извлекать и объединять данные из одной или нескольких таблиц с помощью одного запроса.
-
Основные пункты нормализации
- Упорядочивание данных в логические группы или наборы.
- Нахождение связей между наборами данных (например «один-ко-многим» и «многие-ко-многим»).
- Минимизация избыточности данных.
-
Преимущества нормализованной базы данных
- Можно производить сложные выборки данных относительно простыми SQL-запросами.
- Целостность данных — позволяет надежно хранить данные.
- Предотвращает появление «избыточности» хранимых данных. Данные всегда хранятся только в одном месте, что делает легким процесс вставки, обновления и удаления данных.
- Масштабируемость – возможность системы справляться с будущим ростом. База должна работать быстро, когда число пользователей и объемы данных возрастают.
-
Основные принципы организации данных в хранилище
- Каждая сущность хранится в своём редьюсере.
- Коллекция сущностей одного типа хранится в виде объекта, где ключи — идентификаторы объектов, а значения — сами объекты.
- Порядок данных в этом объекте задаётся отдельным массивом состоящим только из идентификаторов.
- Данные ссылаются друг на друга только по идентификаторам.
- Каждая сущность хранится в своём собственном редьюсере.
- Объект entities хранит сами сущности, а ids - идентификаторы.
-
Преимущества
- Данные не повторяются — достаточно поменять только одно место при их изменении
- Редьюсеры не имеют вложенности
- Данные в таком виде легко извлекать и модифицировать
-
В Redux рекомендуются
-
использовать минимальное состояние хранилища
-
извлекать из него данные только по мере необходимости.
-
относиться к хранилищу как к базе данных
-
хранить данные в максимально нормлизованным, без вложений и….
-
хранить каждую сущность в объекте, хранящемся с идентификатором в качестве ключа. Используйте ID для ссылки на нее из других сущностей или списков.
- То есть хранить, например списки хранить не как массив, а как объект с ключом идентификатором.
- Это явно не удобно для использования в некоторых компонентах, но обеспечивает максимальную производительность.
-
Для того, чтобы извлекать данные для компонентов у нас есть селекторы, которые как раз нормализуют данные.
-
Итого, используем селекторы + Reselect для мемоизации (чтоб не высчитывать данные каждый раз, а только если поменялись зависимости)
-
-
Почему в Redux рекомендуется использовать концепции реляционных баз данных и нормализацию?
- Когда часть данных дублируется в нескольких местах, становится сложнее убедиться, что она обновляется надлежащим образом.
- Вложенные данные означают, что соответствующая логика редуктора должна быть более вложенной и, следовательно, более сложной. В частности, попытка обновить глубоко вложенное поле может очень быстро стать очень уродливой.
- Поскольку для неизменяемых обновлений данных также требуется копирование и обновление всех предков в дереве состояний, а новые ссылки на объекты приведут к повторному отображению подключенных компонентов пользовательского интерфейса, обновление глубоко вложенного объекта данных может привести к повторному отображению совершенно не связанных компонентов пользовательского интерфейса, даже если данные, которые они отображают, не были скопированы.на самом деле изменилось.
- Из-за этого рекомендуемый подход к управлению реляционными или вложенными данными в хранилище Redux заключается в том, чтобы обрабатывать часть вашего хранилища, как если бы это была база данных, и сохранять эти данные в нормализованной форме.
-
Основные концепции нормализации данных
- Каждый тип данных получает свою собственную "таблицу" в состоянии.
- Каждая "таблица данных" должна хранить отдельные элементы в объекте, с идентификаторами элементов в качестве ключей и самих элементов в качестве значений.
- Любые ссылки на отдельные элементы должны выполняться путем сохранения идентификатора элемента.
- Для указания порядка следует использовать массивы идентификаторов.
-
Преимущества
- В целом эта структура состояний намного более плоская.
- По сравнению с исходным вложенным форматом это улучшение в нескольких отношениях:
-
- Поскольку каждый элемент определен только в одном месте, нам не нужно пытаться вносить изменения в нескольких местах, если этот элемент обновляется.
- Логика редуктора не должна иметь дело с глубокими уровнями вложенности, поэтому, вероятно, будет намного проще.
- Логика для извлечения или обновления данного элемента теперь довольно проста и последовательна. Учитывая тип элемента и его идентификатор, мы можем напрямую найти его за пару простых шагов, без необходимости копаться в других объектах, чтобы найти его.
- Поскольку каждый тип данных разделен, обновление, подобное изменению текста комментария, потребует только новых копий части дерева "комментарии > byId > комментарий".
- Обычно это будет означать меньшее количество частей пользовательского интерфейса, которые необходимо обновить, поскольку их данные изменились. В отличие от этого, обновление комментария в исходной вложенной форме потребовало бы обновления объекта комментария, родительского объекта post, массива всех объектов post и, вероятно, привело бы к повторному отображению всех компонентов Post и компонентов комментариев в пользовательском интерфейсе.
- Обратите внимание, что нормализованная структура состояний обычно подразумевает, что подключено больше компонентов, и каждый компонент отвечает за поиск своих собственных данных, в отличие от нескольких подключенных компонентов, которые просматривают большие объемы данных и передают все эти данные вниз. Как оказалось, подключенные родительские компоненты просто передают идентификаторы элементов подключенным дочерним элементам - это хороший шаблон для оптимизации производительности пользовательского интерфейса в приложении React Redux, поэтому нормализация состояния играет ключевую роль в повышении производительности.
-
Упорядочивание нормализованных данных в state
- Типичное приложение, скорее всего, будет иметь смесь реляционных и нереляционных данных. Хотя не существует единого правила, точно определяющего, как должны быть организованы эти разные типы данных, одним из распространенных шаблонов является помещение реляционных "таблиц" под общий родительский ключ, такой как "entities".
- Это может быть расширено несколькими способами. Например, приложение, которое выполняет большое редактирование объектов, может захотеть сохранить два набора "таблиц" в состоянии, один для "текущих" значений элементов и один для "незавершенных" значений элементов. Когда элемент редактируется, его значения могут быть скопированы в раздел "незавершенное производство", и любые действия, которые его обновляют, будут применены к копии "незавершенного производства", позволяя форме редактирования управляться этим набором данных, в то время как другая часть пользовательского интерфейсапо-прежнему ссылается на оригинальную версию. "Сброс" формы редактирования просто потребовал бы удаления элемента из раздела "незавершенное производство" и повторного копирования исходных данных из "текущего" в "незавершенное производство", в то время как "применение" изменений потребовало бы копирования значений из "незавершенного производства".переход от раздела "прогресс" к разделу "текущий".
-
Взаимосвязи и таблицы
- Поскольку мы рассматриваем часть нашего хранилища Redux как "базу данных", многие принципы проектирования баз данных также применимы и здесь. Например, если у нас есть связь "многие ко многим", мы можем смоделировать ее, используя промежуточную таблицу, в которой хранятся идентификаторы соответствующих элементов (часто называемую "таблицей соединений" или "ассоциативной таблицей"). Для согласованности мы, вероятно, также хотели бы использовать тот же byIdallIdsподход и, который мы использовали для фактических таблиц элементов.
- Такие операции, как "Поиск всех книг этого автора", могут быть легко выполнены с помощью одного цикла над таблицей соединений. Учитывая типичные объемы данных в клиентском приложении и скорость движков Javascript, этот вид операций, вероятно, будет иметь достаточно высокую производительность для большинства вариантов использования.
-
Нормализация вложенных данных
- Поскольку API-интерфейсы часто отправляют обратно данные во вложенной форме, эти данные необходимо преобразовать в нормализованную форму, прежде чем их можно будет включить в дерево состояний. Для этой задачи обычно используется библиотека Normalizr. Вы можете определить типы схем и отношения, передать схему и данные ответа в Normalizr, и он выведет нормализованное преобразование ответа. Затем этот вывод можно включить в действие и использовать для обновления хранилища. Смотрите документацию Normalizr для получения более подробной информации о его использовании.
Быстродействие и оптимизация Redux **
- Ссылки
Асинхронные операции в Redux
-
Асинхронные операции
- Асинхронные операции - операции, требующие некоторого времени для завершения.
- Например - функция, которая делает запрос API на сервер. Она не возвращает результат немедленно, для получения ответа от сервера требуется несколько секунд.
- Поэтому, если вы вызываете эту функцию и присваиваете ее значение для некоторой переменной, она будет undefined не определено. Поскольку Javascript не знает, что функция обрабатывает некоторые операции async.
- Про декларативное программирование и функциональные/классовые компоненты
- В мире реакта мы используем декларативное программирование. То есть мы не описываем что нужно убрать этот текст или напротив - добавить текст в HTML. Мы описываем состояние компонента.
- Для того, чтобы описывать состояния компонента, нам недостаточно использовать такой простой синтаксис описания компонента в виде функции.
- Такой синтаксис подходит только для очень простых компонентов, которые имеют небольшую часть логики Stateless компоненты.
- Для того, чтобы добавить описание состоянию компонента нам придется использовать второй синтаксис с использование классов ES6
-
Запрос внутри actionCreator
-
Самый простой вариант - делаем запрос внутри actionCreator
-
Например, при помощи fetch:
-
const fetchDog = (dispatch) => { dispatch(requestDog()); return fetch('https://dog.ceo/api/breeds/image/random') .then(res => res.json()) .then( data => dispatch(requestDogSuccess(data)), err => dispatch(requestDogError()) ); };
-
-
Это простой, но очень негибкий подход.
-
Ядро Redux это контейнер состояния (state container), который поддерживает только синхронные потоки данных.
-
В случае асинхронного вызова, надо сначала дождаться ответа и затем (если не было ошибок) обновить состояние. А если у приложения сложная логика?
-
Для этого Redux использует промежуточные слои (middlewares) - код, который выполняется после отправки действия, но перед вызовом редюсера.
-
Промежуточные слои могут соединяться в цепочку вызовов для различной обработки действия (action), но на выходе обязательно должен быть простой объект (действие)
-
-
Middlewares
-
Промежуточные слои Redux. Используются для реализации асинхронности в Redux
-
функция, которая запускается каждый раз при отправке action’а
-
Ядро Redux это контейнер состояния (state container), который поддерживает только синхронные потоки данных.
-
На каждое действие, в хранилище (store) посылается объект, описывающий что произошло, затем вызывается редюсер (reducer) и состояние (state) сразу обновляется.
-
Промежуточный слой это кусок кода, который выполняется после отправки действия, но перед вызовом редюсера.
-
Промежуточные слои могут соединяться в цепочку вызовов для различной обработки действия (action), но на выходе обязательно должен быть простой объект (действие)
-
Для асинхронных операций, Redux предлагает использовать redux-thunk промежуточный слой.
-
Написание собственной middleware не так сложно, как может показаться, и позволяет использовать некоторые мощные средства.
-
Например:
- Хотите посылать API-запрос каждый раз, когда имя action’a начинается с FETCH_? Вы можете сделать это с помощью middleware.
- Хотите централизованное место для логирования событий в вашем аналитическом ПО? Middleware — хорошее место для этого.
- Хотите предотвратить запуск action’a в определенный момент времени? Вы можете сделать это с помощью middleware, невидимого для остальной части вашего приложения.
- Хотите перехватить action, имеющий токен JWT, и автоматически сохранить его в localStorage? Да, middleware.
-
-
Redux-thunk
-
библиотека, один из вариантов реализации middleware (промежуточный слой) для React-Redux
-
thunk = преобразователь (англ)
-
стандартный путь выполнения асинхронных операций в Redux.
-
вводит понятие функции-преобразователя, которая вызывается внутри dispatch и уже по завершении своей работы возвращает нормлаьный dispatch (вызовет необходимый метод для изменения store)
-
вызываем dispatch, как обычно. Но передаем в него не обьект, а функцию-1, которая возвращает функцию-2. В возвращаемой функции-2 есть аргумент dispatch. Теперь мы можем в этой функции-1 делать любые асинхронные операции и вызывать dispatch тогда, когда нам нужно.
-
Преимуществом использования redux-thunk является то, что компонент не знает, что выполняется асинхронное действие. Т.к. промежуточный слой автоматически передает функцию dispatch в функцию, которую возвращает генератор действий, то снаружи, для компонента, нет никакой разницы в вызове синхронных и асинхронных действий (и компонентам больше не нужно об этом беспокоиться)
-
thunk = функция, которая выполняет асинхронную операцию и на выходе диспатчит какие-то action в reducers. Саму функцию thunk тоже можно задиспатчить По сути, thunk = название функции, в которой происходит какая-то логика. Эта функция производит какие-то асинхронные действия и при этом умеет вызывать различные dispatch по результатам этих асинхронных действий. Чтобы она могла вызывать метод dispatch, он должен прийти в неё - т.е. dispatch надо передать в параметрах этой функции при её вызове Функцию thunk запускает Redux. Мы её диспатчим, а Redux store её запустит и закинет в неё свой метод dispatch.
А откуда функция thunk получит данные, которые должна обработать? Например, текст сообщения, которое она должна послать AJAX'ом на сервер? Передать эти данные как параметр функции мы не можем, т.к. вызывать её будет store. Эти данные она возьмёт из замыкания. Чтоб возникло замыкание, функцию thunk надо вернуть из некоей родительской функции (тогда thunk получит доступ к данным родительской функции). Используем родительскую функцию ThunkCreator:
- В ThunkCreator передаём данные (для передачи AJAX'ом на сервер, например),
- ThunkCreator вернёт нам thunk (уже с замыканием в котором есть нужные данные).
- полученную функцию thunk мы диспатчим в Redux store
- Redux при вызове thunk передаст в неё метод dispatch (чтоб thunk могла по результатам своей работы что-то задиспатчить в store) Т.е. по факту, мы:
- диспатчим вызов ThunkCreator, в который передаём данные
- ThunkCreator вызовет thunk (данные уже в нём благодаря замыканию),
- thunk выполнит AJAX-логику
- и по результатам вызовет какие-то dispatch, которые уйдут в reducers.
Один момент: store не умеет принимать функции (он ждёт объект со свойством type, чтоб раскидать по reducers). То есть, store не может принять thunk :( Поэтому, приходится использовать middleware (промежуточный слой) - он вклинивается между приёмником dispatch в store и моментом передачи диспатчей по reducers. Мы должны при создании store немного его перенастроить, чтоб добавить middleware в цепочку. Получается такая логика:
- если на вход поступил обычный dispatch - он проходит middleware насквозь и уходит в reducers.
- но, если на входе пришла функция (thunk) - она обрабатывается middleware, и её результаты снова отправляются на вход Store.
- если эти результаты = ещё один thunk, то процесс повторяется (да, thunk могут быть вложенными)
- если эти результаты = dispatch, то он проходит middleware насквозь и уходит в reducers.
-
По умолчанию, экшены в Redux являются синхронными, что, является проблемой для приложения, которому нужно взаимодействовать с серверным API, или выполнять другие асинхронные действия. К счастью Redux предоставляет нам такую штуку как middleware, которая стоит между диспатчом экшена и редюсером. Существует две самые популярные middleware библиотеки для асинхронных экшенов в Redux, это — Redux Thunk и Redux Saga.
-
Это библиотека нацеленная делать сайд-эффекты проще и лучше
-
Это middleware библиотека, которая позволяет вам вызвать action creator, возвращая при этом функцию вместо объекта. Функция принимает метод dispatch как аргумент, чтобы после того, как асинхронная операция завершится, использовать его для диспатчинга обычного синхронного экшена, внутри тела функции.
-
Обычно Redux-Thunk используют для асинхронных запросов к внешней API, для получения или сохранения данных.
-
Например, у нас есть обычное todo приложение. Когда мы нажимаем «добавить todo», обычно, сперва диспатчится экшен, который сообщает о старте добавления нового todo. Затем, если todo элемент успешно создан и возвращен сервером, диспатчится другой экшен, с нашим новым todo элементом, и операция завершается успешно. В случае, если сервер по каким то причинам возвращает ошибку, то вместо добавления нового todo диспатчится экшен с ошибкой, что операция не была завершена.
-
Давайте посмотрим, как это может быть реализовано с помощью Redux-Thunk. В компоненте, экшен диспатчится как обычно
-
В самом экшене дело обстоит намного интереснее. Здесь мы будем использовать библиотеку Axios, для ajax запросов
-
Мы будем делать POST запрос на адрес — jsonplaceholder.typicode.com/todos
-
Наш addTodo action creator возвращает функцию, вместо обычного экшен объекта. Эта функция принимает аргумент dispatch из store.
-
Внутри тела функции мы сперва диспатчим обычный синхронный экшен, который сообщает, что мы начали добавление нового todo с помощью внешней API. Простыми словами — запрос был отправлен на сервер. Затем, мы собственно делаем POST запрос на сервер использую Axios. В случае утвердительного ответа от сервера, мы диспатчим синхронный экшен, используя данные, полученные из сервера. Но в случае ошибки от сервера мы диспатчим другой синхронный экшен с сообщением ошибки.
-
Функция, возвращаемая асинхронным action creator'ом с помощью Redux-Thunk, также принимает getState метод как второй аргумент, что позволяет получать стейт прямо внутри action creator'а
-
Использование getState может быть действительно полезным, когда надо реагировать по разному, в зависимости от текущего стейта. Например, если мы ограничили максимальное количество todo элементов до 4, мы можем просто выйти из функции, если этот лимит превышается:
-
Ссылки
- https://habr.com/ru/post/351168/
- https://monsterlessons.com/project/lessons/reduxjs-asinhronnye-eksheny-s-pomoshyu-redux-thunk
- https://tuhub.ru/posts/redux-i-thunk-vmeste-react-rukovodstvo-dlya-chajnikov
- https://www.youtube.com/watch?v=eWdnjfRu9Io
- Redux и Thunk вместе с React. Руководство для чайников.
-
-
Redux saga
-
другая библиотека, для реализации middleware (промежуточный слой) React-Redux
-
Для упрощения и улучшения сайд-эффектов в приложениях React-Redux. Прежде всего - асинхронные запросы (извлечение данных и т.д.) и нечистые вещи (доступ к кешу браузера и т.д.)
-
Их легче тестировать, на них легче реализовать сложную логику (задержки, параллельные задачи, отмена задач,)
-
Саги это
дизайн паттерн
, пришел из мира распределенных транзакций. Сага управляет процессами, которые необходимо выполнять транзакционным способом, сохраняя состояние выполнения и компенсируя неудачные процессы. -
Работают на основе функций-генераторов
-
Если говорить в общем, мы имеем сагу чья работа это следить за отправленными действиями (dispatched actions). И ещё одна сага-рабочий
-
Сага-наблюдатель (watcher saga) является ещё одним неявным слоем. Дает больше гибкости для реализации сложной логики, но иногда лишняя для простых приложений.
-
Effects. Методы внутри саг(?) возвращают не dispatch action, а объекты с инструкциями для промежуточного слоя (middleware) — отправить действие. Эти возвращаемые объекты называются Эффекты (Effects)
-
Альтернативы redux-saga. Две самых популярных:
- redux-observable (базируется на RxJS)
- redux-logic (базируется на RxJS наблюдателях, но даёт свободу писать логику в других стилях).
-
Ссылки
- Кузебюрдин (IT-Kamasutra) про Саги
- посмотреть Применения паттерна Сага от Caitie McCaffrey,
- статья, которая первая описывает саги в отношении распределенных систем (если вы амбициозны)
-
-
Axios
- Инструмент для отправки ajax-запросов, основанный на промисах, очень похожий на jQuery.
- Альтернативы: got, fetch, SuperAgent, jQuery
-
Вариант организации AJAX (IT-Kamasutra)
-
для работы с серверным API & AJAX - используем axios
-
Изначально у нас в стэйте нет данных (например, списка задач) - мы должны получить их с сервера
-
Берём reducer, в котором эти данные выводятся и соответствующий action
-
Создаём новый action = setTasks //получить-установить задачу
-
В reducer пишем реакцию на этот action - добавить в state данные из объекта, который приходит с этим action
-
В контейнерной компоненте, в функции mapStateToProps добавляем в state компоненты задачи из общего state
-
В контейнерной компоненте, в функции mapDispatchToProps создаём callback для вызова этого action. При вызове этого action - он добавит задачи в state
-
Сам факт захода пользователя на страницу со списком задач = действие (т.е. диспатч), которое вызывает этот action.
-
Ссылки
-
Ссылки
- Оф. документация React-redux (en)
- Оф. документация React-redux (ru)
- Оф. документация React-redux (ru, v2 - GitHub)
- Оф. документация Redux Devtools (ru)
- Изучаем Redux на примере создания мини-Redux
- Создаем свой собственный Redux, часть 2: функция connect
- Build Yourself a Redux (en)
- Redux и Thunk вместе с React. Руководство для чайников