Функция createReducer() похожа на функцию создания поисковой таблицы из документации по Redux. В ней используется библиотека immer, что позволяет писать “мутирующий” код, обновляющий состояние иммутабельно. Это защищает от непреднамеренного мутирования состояния в редукторе.

Любой редуктор, в котором используется инструкция switch, может быть преобразован с помощью createReducer(). Каждый case становится ключом объекта, передаваемого в createReducer(). Иммутабельные обновления, такие как распаковка объектов или копирование массивов, могут быть преобразованы в “мутации”. Но это не обязательно: можно оставить все как есть.

Ниже приводится пример использования createReducer(). Начнем с типичного редуктора для списка задач, в котором используется инструкция switch и иммутабельные обновления:

function todosReducer(state = [], action) {
  switch (action.type) {
    case 'ADD_TODO': {
      return state.concat(action.payload);
    }
    case 'TOGGLE_TODO': {
      const { index } = action.payload;
      return state.map((todo, i) => {
        if (i !== index) return todo;
 
        return {
          ...todo,
          completed: !todo.completed,
        };
      });
    }
    case 'REMOVE_TODO': {
      return state.filter(
        (todo, i) => i !== action.payload.index
      );
    }
    default:
      return state;
  }
}

Обратите внимание, что мы вызываем state.concat() для получения копии массива с новой задачей, state.map() также для получения копии массива, и используем оператор spread для создания копии задачи, подлежащей обновлению.

С помощью createReducer() мы можем сократить приведенный пример следующим образом:

const todosReducer = createReducer([], (builder) => {
  builder
    .addCase('ADD_TODO', (state, action) => {
      // "мутируем" массив, вызывая push()
      state.push(action.payload);
    })
    .addCase('TOGGLE_TODO', (state, action) => {
      const todo = state[action.payload.index];
      // "мутируем" объект, перезаписывая его поле `completed`
      todo.completed = !todo.completed;
    })
    .addCase('REMOVE_TODO', (state, action) => {
      // мы по-прежнему можем использовать иммутабельную логику обновления состояния
      return state.filter(
        (todo, i) => i !== action.payload.index
      );
    });
});

Возможность “мутировать” состояние особенно полезна при необходимости обновления глубоко вложенных свойств. Например, такой код:

case 'UPDATE_VALUE':
  return {
    ...state,
    first: {
      ...state.first,
      second: {
        ...state.first.second,
        [action.someId]: {
          ...state.first.second[action.someId],
          fourth: action.someValue
        }
      }
    }
  }
 

Можно упростить так:

updateValue(state, action) {
  const { someId, someValue } = action.payload
  state.first.second[someId].fourth = someValue
}
 

Функция createReducer() может быть очень полезной, но следует помнить о том, что:

  • “Мутирующий” код правильно работает только внутри createReducer()
  • Immer не позволяет смешивать “мутирование” черновика (draft) состояния и возвращение нового состояния

**Объединение reducers и последовательный их вызов

import { createReducer } from '@reduxjs/toolkit';
 
// Редьюсер 1
const reducer1 = createReducer(initialState1, (builder) => {
  builder
    .addCase(actionType1, (state, action) => {
      // Обработка действия actionType1
    })
    .addCase(actionType2, (state, action) => {
      // Обработка действия actionType2
    });
});
 
// Редьюсер 2
const reducer2 = createReducer(initialState2, (builder) => {
  builder
    .addCase(actionType3, (state, action) => {
      // Обработка действия actionType3
    })
    .addCase(actionType4, (state, action) => {
      // Обработка действия actionType4
    });
});
 
// Объединение редьюсеров
const combinedReducer = createReducer(
  {},
  (builder) => {
    builder
      .addCase(actionType5, reducer1) // Вызов редьюсера 1 для действия actionType5
      .addCase(actionType6, reducer2); // Вызов редьюсера 2 для действия actionType6
  }
);

Назад