Логіка Reducers
Логіка 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, а не вкладеними масивами).
item.subtasks[0].completed = true. Але в класичному Redux ви зобов'язані страждати (або використовувати утиліти для іммутабельності).Далі ми розглянемо, як розбивати один великий редюсер на багато маленьких.
Actions, Constants та Action Creators
У попередньому прикладі ми писали об'єкти action "на місці" і використовували рядки 'counter/increment' прямо в коді. У великому проєкті це прямий шлях до помилок.
Комбінування Reducers (Root Reducer)
У реальному додатку ви не будете тримати всю логіку в одному файлі reducer.js. Це зробило б файл безкінечним і нечитабельним.