Мы снова вместе со 2-м эпизодом сериала «Объяснение». В этом разделе я попытаюсь объяснить одну из самых важных концепций React: Redux и Redux Toolkit. В моей предыдущей статье я рассказал вам о React Context для управления состоянием в React. Так зачем нам еще один инструмент управления состоянием, если мы уже знаем React Context?

Зачем нам нужна другая система управления состоянием, отличная от React Context?

У React Context были некоторые недостатки. Мы писали функции поставщика контекста вместе для связанных состояний и отдельно для несвязанных. Подобно контексту авторизации для информации о пользователе и операциях входа в систему, x-контекст для других состояний x. Мы оборачивали эти провайдеры контекста вокруг компонентов в index.js. Когда у нас было большое количество функций провайдера, мы столкнулись с такой картинкой:

<WContextProvider>
  <XContextProvider>
    <YContextProvider>
      <ZContextProvider>
          <App />
      </ZContextProvider>
    </YContextProvider>
  </XContextProvider>
</WContextProvider>

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

Еще одним недостатком React Context является то, что он не оптимизирован для частого изменения состояния. Если у вас большой проект и ваше состояние часто обновляется, выбор React Context здесь может быть неправильным выбором. Я хотел бы уточнить этот момент, нет причин выбирать Redux для проекта базового уровня, React Context вполне справится с этой задачей. Если вы думаете, что это будет большой проект, в начале или если ваш проект вырос, вы можете включить Redux в свой проект. Также хочу отметить, что вы можете использовать и React Context, и Redux в одном проекте, в этом нет ничего плохого. Вы можете построить весь свой проект с помощью React Context и реализовать Redux для часто меняющегося состояния.

Что такое Redux?

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

Поток данных всегда идет от центрального хранилища данных (CDS) к компонентам. У меня может быть только одно хранилище в одном проекте. Компоненты не могут изменять хранилище данных напрямую. Я обеспечу связь между моим хранилищем данных и компонентами с функциями редуктора. Эта функция редуктора не является функцией useReducer(), о которой вы узнали. Их общее название — функции редуктора.

Я буду запускать свои функции редуктора с действиями, в которых я буду писать компоненты. Функции редуктора будут делать обновления в моих состояниях. Теперь я хочу показать вам на примере, что такое функция редьюсера.

const initialState = { counter: 0, showCounter: true };

const counterReducer = (state = initialState, action) => { //my reducer func.
  if (action.type === "increaseByOne") { //when i trigger this action from my component, this block will run
    return {
      ...state,
      counter: state.counter + 1,
    };
  }
...
};

Глядя на приведенный выше пример, значение счетчика увеличится на 1, когда будет запущено действие «увеличить на один». Я хочу, чтобы вы обратили внимание на то, что у меня есть другие переменные состояния, которые не изменяются с помощью оператора распространения. Если бы я не получил другие переменные, в моем новом состоянии была бы просто одна переменная-счетчик. Даже если я не изменяю другие состояния в действии, я должен пересылать другие переменные состояния. Для нас это проблема, мы каждый раз делаем копию состояния и пересылаем. Есть еще три основные проблемы с Redux:

  • «Настройка магазина Redux слишком сложна»
  • «Мне нужно добавить много пакетов, чтобы заставить Redux делать что-то полезное»
  • «Redux требует слишком много шаблонного кода»

У команды Redux есть решение под названием Redux Toolkit для вышеуказанных проблем.

Инструментарий Redux

Redux Toolkit — это дополнительный пакет, разработанный той же командой, что и Redux. Это позволит нам легче и проще управлять нашим управлением состоянием. Redux Toolkit является официальной рекомендацией с 2019 года.

Если я сделаю пример действия в приведенной выше функции редуктора с помощью Redux Toolkit, он будет таким:

const initialCounterState = { counter: 0, showCounter: true };

const counterSlice = createSlice({ //We create slices for related states in the Redux Toolkit, and each slice has a name. A slice is the portion of Redux code that relates to a specific set of data and actions within the store 's state 
  name: "counter",
  initialState: initialCounterState,
  reducers: {
    increaseByOne(state) { //You don't need to write if check or switch case. increaseByOne is now my reducer function
      state.counter++; //i just update the state i changed.
    }
}

Как вы можете видеть выше, я не обновляю состояние и не пересылаю состояние, которое я не изменил в своей функции редуктора, я просто обновляю состояние, которое я изменил. createSlice использует библиотеку Immer Insider. Immer использует специальный инструмент JS, называемый прокси, для переноса предоставленных вами данных и позволяет вам написать код, который «мутирует» эти упакованные данные. Вот почему функция createSlice в Redux Toolkit позволяет нам проще писать неизменяемые обновления.

Давайте объясним Redux и Redux Toolkit на примере проекта

Теперь я объясню вам, как использовать Redux и Redux Toolkit. Я взял этот пример у Дэйва Грея, одного из разработчиков, к которому я отношу себя. У нас есть одна переменная-счетчик, и мы будем увеличивать или уменьшать этот счетчик на единицу или на определенное значение. Мы можем сбросить его, если нажмем кнопку сброса. Я немного рефакторил проект, добавил дополнительный initialState с именем showCoutner и кнопку, которую мы изменим на true-false. Вы можете найти исходный код проекта здесь и видео здесь. Мы напишем функции-редюсеры, создадим хранилище и вызовем редьюсеры из компонентов.

Теперь давайте запустим проект с React — CRA и скачаем необходимые пакеты @npm install redux и @npm install @reduxjs/toolkit. Когда наш проект будет готов, давайте подготовим файловую структуру.

>src
    JS|App.js                      -----> the top-level React component
    JS|index.js                    -----> the starting point for the app
    >app
          JS|store.js              -----> creates the Redux store instance
    >features
          >counter
              JS|Counter.js        -----> a React component that shows the UI for the counter feature
              JS|counterSlice.js   -----> the Redux logic for the counter feature

Создать центральное хранилище данных Redux

В Redux есть одно центральное хранилище данных. Сначала создадим его, проанализируем, что происходит и как это выглядит.

Хранилище Redux создается с помощью функции configureStore из Redux Toolkit. configureStore требует, чтобы мы передали аргумент редуктора. Мы назначаем функции редуктора, необходимые для нашего приложения, в аргумент редуктора, например, в этом проекте мы использовали счетчикимя для доступа к состоянию и функциям, где мы написали в counterSlice, и мы присвоили counterSlice аргументу редуктора. В этом проекте не было необходимости назначать более одной функции редуктора, но при необходимости мы могли также назначить другую функцию редуктора, которую мы создали в папке функций. Например, если бы в папке functions была папка Login, а под ней Login.js и loginSlice.js, мы могли бы импортировать loginSlice.js в это хранилище и назвать его Login: loginReducer, чтобы мы могли используйте функции и состояния в loginSlice в каждой точке приложения.

Предоставьте свое хранилище данных для вашего приложения

Мы создали наше хранилище данных, теперь нам нужно указать его в index.js, чтобы наше приложение знало об этом, и мы могли использовать его в компонентах.

import { store } from './app/store';
import { Provider } from 'react-redux';

ReactDOM.render(
    <Provider store={store}> //like this
      <App />
    </Provider>
   ...
);

Создать фрагмент

Мы не можем получить доступ и обновить наши состояния в слайсе напрямую из компонента, мы запишем их в наш слайс и обновим его с помощью функций редуктора. Мы создадим файлы фрагментов в соответствии с состояниями, которые будут использоваться в соответствующих компонентах. Например, нам не нужны никакие данные, связанные с логином или пользователем, в компоненте счетчика, мы не будем и не должны создавать функцию с именем логин внутри функций редуктора в файле counterSlice. Когда мы смотрим на приведенный выше пример приложения, нам нужны 2 состояния: одно — это переменная счетчика, а другое — переменная showCounter, которая позволит отображать счетчик. Поскольку обе переменные связаны со счетчиком, мы можем сохранить их обе в нашем файле counterSlice, который мы создадим. Мы можем писать функции синхронизации только в функциях редуктора. Мы напишем thunk-функцию для асинхронной логики и обсудим ее в отдельной теме, сейчас сосредоточимся только на синхронизирующих функциях.

Применение

Мы создали наше центральное хранилище данных, сделали его пригодным для использования в нашем приложении, написали наш файл среза, и теперь мы приступаем к использованию его в компоненте. То, что нам нужно сделать, довольно просто. Мы достигнем наших значений состояния с помощью useSelector. Мы импортируем наши функции редуктора из файла среза и запускаем их с помощью useDispatch.

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

Асинхронные функции с AsyncThunkMiddleware в Redux Toolkit

Все наши функции редуктора выше были функциями синхронизации. Итак, как и где мы пишем асинхронные функции? Я говорил ранее, что мы не можем записать его в раздел редьюсеров. Нам поможет функция промежуточного программного обеспечения под названием AsyncThunk для асинхронных функций в Redux Toolkit. В этом проекте я снова буду использовать Dave Gray — серию Redux Toolkit. Исходный код проекта вы можете найти здесь. В этом проекте мы будем выполнять асинхронные операции, такие как получение, публикация с аксиомами на https://jsonplaceholder.typicode.com. На https://jsonplaceholder.typicode.com/posts есть 100 сообщений, как показано ниже.

{
    "userId": 1,
    "id": 1,
    "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
    "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
  },
  {
    "userId": 1,
    "id": 2,
    "title": "qui est esse",
    "body": "est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla"
  }

создайте промежуточное ПО Async Thunk

Прежде всего, мы определяем асинхронную функцию с помощью функции createAsyncThunk Redux Toolkit. Эта функция createAsyncThunk принимает 2 параметра, первый — это параметр, связанный с операцией, которую мы будем выполнять в виде строки, а во втором мы пишем нашу асинхронную функцию. Мы пишем промежуточное ПО createAsyncThunk в нашем файле среза, но вне нашей функции createSlice. Мы создадим кейсы построителя с помощью extraReducers и будем использовать возвращаемые здесь значения для обновлений нашего состояния позже в нашей функции createSlice. Не волнуйтесь, я объясню.

Выше я показал пример того, как написать промежуточную функцию createAsyncTunk. Мы отправили запрос на получение с axios на указанный выше URL-адрес. Я вернул ответ от API, чтобы обновить свои состояния в моем фрагменте. Теперь давайте посмотрим на кейсы строителя в моем фрагменте. В случаях со сборщиком я буду обновлять свои состояния в соответствии с ответом.

Здесь мы получаем данные (в случаях с билдером), где мы вернули их в функции createAsyncThunk. Мы можем добавить 3 случая для каждого метода. Если вы хотите управлять асинхронной функцией, которую мы написали, во всех случаях (ожидающих, выполненных или отклоненных) отдельно, вы можете использовать ее или просто обрабатывать ее как выполненную, решать вам. Как видите, я обработал операцию асинхронной функции с тремя отдельными случаями в своем фрагменте выше и соответственно обновил свои состояния для каждого случая.

Теперь давайте отправим метод post в API с помощью axios, таким же образом сначала напишем нашу асинхронную функцию.

На что я хочу, чтобы вы обратили внимание на изображении выше, так это на то, как я получаю данные (сообщения) в функции createAsyncThunk, которые я назначу методу addNewPost из компонента.

Теперь давайте возьмем данные, которые мы вернули в createSlice, и обновим наше состояние с помощью кейсов билдера. Я только что добавил «выполнено» здесь, я мог бы также добавить «ожидание» и «отклонить».

Теперь добавим метод удаления.

Теперь давайте обновим наш слайс в кейсах билдера в соответствии с ответом.

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

Как мы будем использовать наши асинхронные функции в компонентах?

Мы написали наши асинхронные функции и обработали их в случаях сборки для обновления состояния. Его также очень просто использовать в компонентах. После импорта наших асинхронных функций туда, где мы их написали в Slice, мы запускаем наши функции с помощью хука useDispatch. Если параметр требуется, не забудьте также присвоить его.

import { addNewPost } from "../../features/posts/postSlice";

  const dispatch = useDispatch();
  dispatch(addNewPost({ title, body: content, userId })).unwrap(); //send the parameters that async function required
---------
import { deletePost } from "../../features/posts/postSlice";

        const dispatch = useDispatch();
        dispatch(deletePost({ id: post.id })).unwrap();

Удачного кодирования :)

Ссылки

www.shawndsilva.com

redux.js.org

Максимилиан Шварцмюллер | Удемы

https://www.codecademy.com