Redux е популярна библиотека за управление на глобалното състояние в уеб приложения. Той осигурява предвидим и последователен начин за актуализиране на състоянието въз основа на действия, изпратени от компоненти. Redux обаче идва и с някои предизвикателства и недостатъци, като например:

  • Писане на много шаблонен код за създаване на действия, редуктори, селектори и магазин
  • Справяне с неизменни актуализации в редуктори
  • Работа с асинхронна логика с междинен софтуер
  • Конфигуриране и отстраняване на грешки в магазина с инструменти за разработка

За да се справи с тези проблеми и да направи разработката на 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 приема стойност на първоначално състояние и обект от редуценти на регистър, зададен от типове действия. Той използва библиотека 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 Middleware

Друго предизвикателство при разработката на Redux е обработката на асинхронна логика с междинен софтуер. Мидълуер са функции, които прихващат всяко действие, изпратено до магазина, и могат да го променят, забавят, отменят или изпълняват допълнителни задачи, преди да го предадат на следващия мидълуер или редуктор. Мидълуерът е полезен за добавяне на допълнителна функционалност като регистриране, отстраняване на грешки, обработка на грешки или асинхронна логика.

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

С помощта на createAsyncThunk и createSlice от RTK,

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

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

  • configureStore, който създава хранилище Redux с някакъв стандартен междинен софтуер, като thunk, сериализируема проверка, неизменна проверка и т.н.
  • createEntityAdapter, който създава многократно използвани селектори и редуктори за управление на нормализирани данни в хранилището.
  • createSelector, който създава мемоизирани селектори за оптимизиране на ефективността на изчисленията на извлечени данни.

В заключение, RTK е предназначен да помогне на разработчиците да пишат по-добър Redux код по-бързо и по-лесно. Не е задължително да използвате RTK с Redux, но е силно препоръчително за повечето случаи на употреба.

Ако искате да научите повече за RTK, можете да посетите официалната му документация на адрес https://redux-toolkit.js.org/