Table of Contents
Что такое Redux? 🧐
Redux - это библиотека для управления и обновления состояния приложения с использованием событий, называемых «actions». Он служит централизованным хранилищем состояния, которое необходимо использовать во всем приложении, с правилами, гарантирующими, что состояние может обновляться только предсказуемым образом.
Почему я должен использовать Redux? 🤔
Redux помогает вам управлять «глобальным» состоянием - состоянием, которое необходимо во многих частях вашего приложения.
Шаблоны и инструменты, предоставляемые Redux, упрощают понимание того, когда, где, почему и как обновляется состояние в вашем приложении, и как логика вашего приложения будет вести себя при возникновении этих изменений. Redux направляет вас к написанию предсказуемого и тестируемого кода, что дает вам уверенность в том, что ваше приложение будет работать должным образом.
Когда мне использовать Redux? 🤔🤯
Redux помогает справиться с управлением общим состоянием, но, как и любой другой инструмент, у него есть компромиссы. Это компромисс между краткосрочной и долгосрочной производительностью. Он также добавляет некоторую косвенность к вашему коду и просит вас соблюдать определенные ограничения.
Redux более полезен, когда:
- У вас есть большое количество состояний приложения, которые необходимы во многих местах приложения.
- Состояние приложения часто обновляется с течением времени
- Логика обновления этого состояния может быть сложной.
- Приложение имеет кодовую базу среднего или большого размера, и над ним могут работать многие люди.
note
Не всем приложениям нужен Redux. Найдите время, чтобы подумать о том, какое приложение вы создаете, и решить, какие инструменты лучше всего помогут решить проблемы, над которыми вы работаете.
Термины и концепции Redux ✍🏻
State Management
Начнем с небольшого счетчика React. Он отслеживает число в состоянии компонента и увеличивает число при нажатии кнопки: c
Value: 0
Это автономное приложение, состоящее из следующих частей:
- Состояние (state), источник истины, который движет нашим приложением;
- Представление (view), декларативное описание пользовательского интерфейса на основе текущего состояния
- Действия (actions), события, которые происходят в приложении на основе ввода данных пользователем, и запускают обновления в состоянии
Это небольшой пример «одностороннего потока данных» (one-way data flow):
- State описывает состояние приложения в определенный момент времени.
- UI отображается на основе этого состояния
- Когда что-то происходит (например, пользователь нажимает кнопку), состояние обновляется в зависимости от того, что произошло.
- UI перерисовывается на основе нового состояния
Однако простота может нарушиться, когда у нас есть несколько компонентов, которые должны совместно использовать одно и то же состояние, особенно если эти компоненты расположены в разных частях приложения. Иногда это можно решить, «подняв состояние» до родительских компонентов, но это не всегда помогает.
Один из способов решить эту проблему - извлечь общее состояние из компонентов и поместить его в централизованное место за пределами дерева компонентов. Благодаря этому наше дерево компонентов становится большим «представлением», и любой компонент может получить доступ к состоянию или запускать действия, независимо от того, где они находятся в дереве!
Определяя и разделяя концепции, участвующие в управлении состоянием и применяя правила, которые поддерживают независимость между представлениями и состояниями, мы придаем нашему коду большую структуру и удобство сопровождения.
Основная идея Redux
Eдиное централизованное место для хранения глобального состояния в вашем приложении и определенные шаблоны, которым необходимо следовать при обновлении этого состояния, чтобы сделать код предсказуемым.
Неизменность (Immutability)
«Mutable» означает «изменчивый». Если что-то «immutable», это никогда не может быть изменено.
caution
Все объекты и массивы JavaScript по умолчанию изменяемы.
Если я создаю объект, я могу изменить содержимое его полей. Если я создам массив, я также могу изменить его содержимое:
const obj = { a: 1, b: 2 };// still the same object outside, but the contents have changedobj.b = 3;const arr = ["a", "b"];// In the same way, we can change the contents of this arrayarr.push("c");arr[1] = "d";
Это называется изменением объекта или массива. Это тот же объект или ссылка на массив в памяти, но теперь содержимое внутри объекта изменилось.
Для неизменного обновления значений ваш код должен делать копии существующих объектов / массивов, а затем изменять копии.
Мы можем сделать это вручную, используя операторы распространения массива / объекта в JavaScript, а также методы массива, которые возвращают новые копии массива вместо изменения исходного массива:
const obj = {a: {// To safely update obj.a.c, we have to copy each piecec: 3,},b: 2,};const obj2 = {// copy obj...obj,// overwrite aa: {// copy obj.a...obj.a,// overwrite cc: 42,},};const arr = ["a", "b"];// Create a new copy of arr, with "c" appended to the endconst arr2 = arr.concat("c");// or, we can make a copy of the original array:const arr3 = arr.slice();// and mutate the copy:arr3.push("c");
Redux ожидает, что все обновления состояния будут содержать новый обьект.
Терминология ✍🏻
Есть несколько важных терминов Redux, с которыми вам необходимо ознакомиться, прежде чем мы продолжим:
Actions
Action - это простой объект JavaScript, имеющий поле типа. Вы можете думать о действии как о событии, которое описывает что-то, что произошло в приложении.
Поле типа должно быть строкой, которая дает этому действию описательное имя, например «todos / todoAdded». Обычно мы пишем эту строку типа как «domain / eventName», где первая часть - это функция или категория, к которой принадлежит это действие, а вторая часть - это конкретное событие, которое произошло.
У объекта действия могут быть другие поля с дополнительной информацией о том, что произошло. По соглашению мы помещаем эту информацию в поле, называемое полезной нагрузкой.
Типичный объект действия может выглядеть так:
const addTodoAction = {type: "todos/todoAdded",payload: "Buy milk",};
Action Creators
Создатель действия - это функция, которая создает и возвращает объект действия. Обычно мы используем их, поэтому нам не нужно каждый раз писать объект действия вручную:
const addTodo = (text) => {return {type: "todos/todoAdded",payload: text,};};
Reducers
Reducer - это функция, которая получает текущее состояние и объект action
, решает, как обновить состояние при необходимости, и возвращает новое состояние: (state, action) => newState.
tip
Функция Reducer получило свое название, потому что похожа на функцию обратного
вызова, которую вы передаете методу Array.reduce()
.
Reducers всегда должны следовать определенным правилам:
- Они должны рассчитывать только новое значение состояния на основе аргументов состояния и действия.
- Им не разрешено изменять существующее состояние. Вместо этого они должны делать неизменяемые обновления, копируя существующее состояние и внося изменения в скопированные значения.
- Они не должны выполнять асинхронную логику, вычислять случайные значения или вызывать другие «побочные эффекты».
Логика внутри функции reducer
обычно следует той же серии шагов:
- Проверить, обрабатывает ли
reducer
это действие.- Если да, сделать копию состояния, обновить копию новыми значениями и вернуть ее.
- В противном случае вернуть существующее состояние без изменений
Вот небольшой пример функции reducer
, показывающий шаги, которым должен следовать каждый reducer
:
const initialState = { value: 0 };function counterReducer(state = initialState, action) {// Проверить, обрабатывает ли `reducer` это действие.if (action.type === "counter/increment") {// Если да, сделать копию состоянияreturn {...state,// обновить копию новыми значениями и вернуть ее.value: state.value + 1,};}//В противном случае вернуть существующее состояние без измененийreturn state;}
Reducers могут использовать любую внутреннюю логику, чтобы решить, каким должно быть новое состояние: if / else, switch, циклы и так далее.
Store
Текущее состояние приложения Redux находится в объекте, называемом store.
Хранилище создается путем передачи функциии reducer
и имеет метод getState, который возвращает текущее значение состояния:
import { configureStore } from "@reduxjs/toolkit";const store = configureStore({ reducer: counterReducer });console.log(store.getState());// {value: 0}
Dispatch
В Redux есть метод, называемый Dispatch.
Единственный способ обновить состояние - вызвать store.dispatch()
и передать action
объект.
Store
запустит свою функцию reducer
и сохранит внутри новое значение состояния, и мы можем вызвать getState()
для получения обновленного значения:
store.dispatch({ type: "counter/increment" });console.log(store.getState());// {value: 1}
tip
Вы можете думать о функции dispatch
как о «запуске события» в приложении.
Что-то случилось, и мы хотим, чтобы об этом узнал store
.
Reducers действуют как прослушиватели событий, и, когда они слышат интересующее их действие, они обновляют состояние в ответ.
Обычно мы вызываем action creators
, чтобы они отправили нужный action
:
const increment = () => {return {type: "counter/increment",};};store.dispatch(increment());console.log(store.getState());// {value: 2}
Selectors
Selectors - это функции, которые знают, как извлекать определенные фрагменты информации из значения состояния хранилища. По мере роста приложения это может помочь избежать повторения логики, поскольку разные части приложения должны читать одни и те же данные:
const selectCounterValue = (state) => state.value;const currentValue = selectCounterValue(store.getState());console.log(currentValue);// 2
Односторонний поток данных (One Way Data Flow) 🧐
Есть несколько шаблонов программирования для структурирования вашего реагирующего приложения при управлении состоянием. Односторонний поток данных ← - сокращение, и однонаправленный путь ← родитель к потомку.
Односторонний поток данных - Parent Child
В приложении React есть два разных шаблона потока данных: первый - родительский, а другой - Redux или некоторая библиотека управления этапами.
В React JS данные передаются в одном направлении, от родительского к дочернему. Это помогает компонентам быть простыми и предсказуемыми.
Поток данных приложения Redux 🧐
Ранее мы говорили об «одностороннем потоке данных», который описывает эту последовательность шагов для обновления приложения:
- state описывает состояние приложения в определенный момент времени.
- UI отображается на основе этого состояния
- Когда что-то происходит (например, пользователь нажимает кнопку), состояние обновляется в зависимости от того, что произошло.
- UI перерисовывается на основе нового состояния
В частности, для Redux мы можем разбить эти шаги более подробно:
Начальная настройка
- Хранилище Redux создается с использованием функции
rootReducer
- Хранилище вызывает
rootReducer
один раз и сохраняет возвращаемое значение в качестве своего начального состояния. - Когда UI впервые отображается, компоненты UI получают доступ к текущему состоянию хранилища Redux и используют эти данные, чтобы решить, что отображать.
- Они также подписываются на любые будущие обновления
store
, чтобы знать, изменилось ли состояние.
- Хранилище Redux создается с использованием функции
Обновления
- Что-то происходит в приложении, например, пользователь нажимает кнопку
- Код приложения отправляет
actions
в хранилище Redux, например отправку({type: 'counter / increment'})
Store
снова запускает функциюreducer
с предыдущим состоянием и текущимaction
и сохраняет возвращаемое значение как новое состояние.Store
уведомляет все части пользовательского интерфейса, на которые подписан, о том, чтоstore
был обновлен.- Каждый компонент пользовательского интерфейса, которому нужны данные из хранилища, проверяет, изменились ли части состояния, которые им нужны.
- Каждый компонент, который видит, что его данные изменились, принудительно выполняет повторный рендеринг с новыми данными, чтобы он мог обновить то, что показано на экране.
Практика 👩💻👨💻
Используя код выше, добавить следующие actions:
- decrement - должен уменьшать значение счетчика
- reset - должен сбрасывать значение счетчика в 0
Результат должен быть выведен в консоль.
Edit this page on GitHub