Redux — популярная библиотека для управления глобальным состоянием в веб-приложениях. Он обеспечивает предсказуемый и согласованный способ обновления состояния на основе действий, отправленных компонентами. Однако Redux также имеет некоторые проблемы и недостатки, такие как:

  • Написание большого количества шаблонного кода для создания действий, редьюсеров, селекторов и хранилища.
  • Работа с неизменяемыми обновлениями в редьюсерах
  • Обработка асинхронной логики с промежуточным ПО
  • Настройка и отладка магазина с помощью devtools

Чтобы решить эти проблемы и сделать разработку Redux проще и быстрее, команда Redux создала Redux Toolkit (RTK), официальный пакет, который содержит инструменты для упрощения общих задач при использовании Redux. RTK включает утилиты для:

  • Создание редьюсеров, действий, селекторов и срезов состояния с меньшим количеством кода
  • Постоянное обновление состояния с использованием библиотеки Immer
  • Обработка асинхронной логики с промежуточным ПО thunk
  • Настройка магазина с промежуточным ПО и инструментами разработки по умолчанию

В этой статье мы рассмотрим, как RTK может помочь нам писать лучшие приложения Redux и ускорить процесс разработки.

Создание редукторов, действий, селекторов и фрагментов

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

RTK предоставляет функции createReducer, createAction, createSelector и createSlice, которые помогают нам создавать эти объекты с меньшим количеством кода. Например,

// Without RTK
const INCREMENT = 'counter/increment';
const DECREMENT = 'counter/decrement';

function increment() {
  return { type: INCREMENT };
}
function decrement() {
  return { type: DECREMENT };
}
function counter(state = 0, action) {
  switch (action.type) {
    case INCREMENT:
      return state + 1;
    case DECREMENT:
      return state - 1;
    default:
      return state;
  }
}

function selectCount(state) {
  return state.counter;
}
// With RTK
import { createReducer , createAction , createSelector , createSlice } from '@reduxjs/toolkit';
const increment = createAction('counter/increment');
const decrement = createAction('counter/decrement');
const counter = createReducer(0 , {
  [increment]: (state) => state + 1,
  [decrement]: (state) => state - 1,
});
const selectCount = createSelector(
  (state) => state.counter,
  (counter) => counter
);
// Or even simpler with createSlice
const counterSlice = createSlice({
  name: 'counter',
  initialState: 0,
  reducers: {
    increment: (state) => state + 1,
    decrement: (state) => state - 1,
  },
});
const { actions , reducer } = counterSlice;
const { increment , decrement } = actions;

Как мы видим из этого примера,

  • createAction генерирует функцию создателя действия, которая возвращает объект действия заданного типа.
  • createReducer принимает начальное значение состояния и объект редукторов case с ключами по типам действий. Он использует внутреннюю библиотеку Immer, чтобы позволить нам писать изменяемую логику обновления без изменения фактического состояния.
  • createSelector создает запоминаемую функцию выбора, которая может эффективно вычислять производные данные из состояния.
  • createSlice объединяет createAction и createReducer в одну функцию, которая создает объект среза, содержащий как действия, так и редьюсер.

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

Неизменяемое обновление состояния с помощью библиотеки Immer

Еще одна проблема в разработке Redux — постоянное обновление состояния в редьюсерах. Неизменяемые обновления означают, что мы не можем напрямую изменить существующий объект состояния, а вместо этого должны создать новый с обновленными значениями. Это гарантирует, что мы не вносим ошибки или несоответствия в наше приложение из-за случайных мутаций.

Однако запись неизменяемых обновлений может быть утомительной и подверженной ошибкам, особенно при работе с вложенными объектами или массивами.

Например,

// Without Immer
function todoReducer(state = [], action) {
switch (action.type) {
case 'ADD TODO’: 
return […state, action.payload];
 case ‘TOGGLE_TODO’:
 return state.map(todo => todo.id === action.payload ? { …todo, completed: !todo.completed } : todo);
 case ‘REMOVE_TODO’: return state.filter(todo => todo.id !== action.payload);
 default: return state; } }

// With Immer import produce from ‘immer’;
function todoReducer(state = [], action) {
 return produce(state, draft => { 
switch (action.type) { 
case ‘ADD_TODO’: draft.push(action.payload);
 break; 
case ‘TOGGLE_TODO’:
 const todo = draft.find(todo => todo.id === action.payload);
 if (todo) { todo.completed = !todo.completed;
 }
 break;
 case ‘REMOVE_TODO’:
 const index = draft.findIndex(todo => todo.id === action.payload);
 if (index > -1) { draft.splice(index, 1);
 }
 break;
 } });
 }

Как мы видим из этого примера,

- `produce` - это функция из библиотеки Immer, которая принимает исходное состояние и функцию обратного вызова в качестве аргументов.
- Функция обратного вызова получает "черновую" версию состояния, которую можно безопасно изменить, не затрагивая исходное состояние. .
- `produce` возвращает новый объект состояния с примененными мутациями.

Используя `produce` от Immer, мы можем написать более простую и читабельную логику обновления, не беспокоясь об неизменности, избежать создания ненужных копий объектов или массивов и использовать встроенные методы JavaScript для манипулирования структурами данных.

Обработка асинхронной логики с помощью промежуточного ПО Thunk

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

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

Используя createAsyncThunk и createSlice от RTK,

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

RTK также предоставляет другие функции, такие как

  • configureStore, который создает хранилище Redux с некоторым промежуточным ПО по умолчанию, таким как преобразователь, сериализуемая проверка, неизменяемая проверка и т. д.
  • createEntityAdapter, который создает многоразовые селекторы и редукторы для управления нормализованными данными в хранилище.
  • createSelector, который создает мемоизированные селекторы для оптимизации производительности вычислений производных данных.

В заключение, RTK призван помочь разработчикам быстрее и проще писать лучший код Redux. Использование RTK с Redux не обязательно, но в большинстве случаев настоятельно рекомендуется.

Если вы хотите узнать больше о RTK, вы можете посетить его официальную документацию по адресу https://redux-toolkit.js.org/.