Classic Redux

Логіка Reducers

Ми вже знаємо, що редюсер — це чиста функція (state, action) => newState. Але як писати складні редюсери, коли стан — це не просто одне число, а складний об'єкт?

Логіка Reducers

Ми вже знаємо, що редюсер — це чиста функція (state, action) => newState. Але як писати складні редюсери, коли стан — це не просто одне число, а складний об'єкт?

Проєктування Структури Стану (State Shape)

Перед тим як писати код, варто подумати про структуру даних.

Поганий приклад:

const initialState = {
  isModalOpen: false,
  userName: 'Ivan',
  todos: [],
  // Все на купу
  filter: 'all',
  loading: false
};

Хороший приклад (Групування за доменами):

const initialState = {
  ui: {
    isModalOpen: false,
    theme: 'dark'
  },
  users: {
    currentUser: { name: 'Ivan' },
    loading: false
  },
  todos: {
    items: [],
    filter: 'all'
  }
};

У цьому уроці ми зосередимося на тому, як редюсер обробляє одну частину цього стану (наприклад, todos).

Патерни обробки Actions

Стандартний патерн — це switch statement.

const initialState = [];

function todosReducer(state = initialState, action) {
  switch (action.type) {
    case 'ADD_TODO':
      // Логіка додавання
      return [...state, action.payload];
      
    case 'TOGGLE_TODO':
      // Логіка оновлення
      return state.map(todo => {
        if (todo.id !== action.payload) return todo;
        return { ...todo, completed: !todo.completed };
      });
      
    case 'REMOVE_TODO':
      // Логіка видалення
      return state.filter(todo => todo.id !== action.payload);

    default:
      // Завжди повертаємо поточний стан, якщо action не наш!
      return state;
  }
}

Чому switch, а не if/else?

Технічно різниці немає. Але switch є загальноприйнятим стандартом у спільноті Redux, оскільки він читабельніший, коли кейсів багато.

Обробка одного Action різними Reducers

Це одна з найпотужніших можливостей Redux. Один Action може бути оброблений декількома Редюсерами одночасно!

Уявіть action LOGOUT.

  • usersReducer повинен очистити інформацію про користувача.
  • todosReducer повинен очистити список завдань.
  • uiReducer повинен скинути тему на дефолтну.

Вам не потрібно діспатчити 3 різні екшени (USER_LOGOUT, TODOS_CLEAR, UI_RESET). Ви відправляєте один action LOGOUT, і кожен редюсер реагує на нього по-своєму.

// todosReducer.js
case 'LOGOUT':
  return []; // Очищаємо тудушки

// usersReducer.js
case 'LOGOUT':
  return null; // Видаляємо юзера

Це робить компоненти незалежними від внутрішньої структури стору. Кнопка "Вихід" просто каже "Користувач вийшов", а система сама знає, що треба почистити.

Складні оновлення (Nested Data)

Як ми говорили раніше, оновлення глибоко вкладених даних — це пекло в класичному Redux.

case 'UPDATE_SUBTASK':
  return {
    ...state,
    items: state.items.map(item => {
      if (item.id !== action.payload.itemId) return item;
      return {
        ...item,
        subtasks: item.subtasks.map(subtask => {
           if (subtask.id !== action.payload.subtaskId) return subtask;
           return { ...subtask, completed: true };
        })
      };
    })
  };

Це жахливо читається і легко допускаються помилки.

Порада для класичного Redux: Намагайтеся тримати стан максимально "пласким" (Flat). Уникайте глибокої вкладеності.

Якщо вам потрібно зберігати складні зв'язки (наприклад, пости і коментарі), використовуйте Нормалізацію даних (зберігати дані як у базі даних, через ID, а не вкладеними масивами).

Redux Toolkit вирішує проблему вкладеності за допомогою Immer, дозволяючи писати item.subtasks[0].completed = true. Але в класичному Redux ви зобов'язані страждати (або використовувати утиліти для іммутабельності).

Далі ми розглянемо, як розбивати один великий редюсер на багато маленьких.

👉 Далі: Комбінування Reducers

Copyright © 2026