Actions, Constants та Action Creators
Actions, Constants та Action Creators
У попередньому прикладі ми писали об'єкти action "на місці" і використовували рядки 'counter/increment' прямо в коді. У великому проєкті це прямий шлях до помилок.
У цьому розділі ми навчимося писати "дорослий" Redux код, розділяючи логіку на правильні шари.
Проблема 1: Магічні рядки
Уявіть, що ви використовуєте рядок 'ADD_TODO' у 10 різних файлах. Одного разу ви зробили помилку:
// Десь в компоненті
dispatch({ type: 'ADD_TODOO', payload: 'Buy milk' });
Redux не видасть помилку. Редюсер просто проігнорує цей action (бо він потрапить у default case). Ви витратите години на дебагінг, не розуміючи, чому Todo не додається.
Рішення: Константи (Action Types)
Ми виносимо всі типи подій в окремі константи.
export const INCREMENT = 'counter/increment';
export const DECREMENT = 'counter/decrement';
export const ADD_TODO = 'todos/add';
Тепер, якщо ви помилитеся в назві змінної (INCREMENTT), ваш IDE або збирач (Webpack/Vite) одразу видасть помилку Variable is not defined. Це fail-fast підхід.
Проблема 2: Дублювання структури
Кожного разу писати повний об'єкт незручно:
dispatch({
type: ADD_TODO,
payload: {
id: Date.now(),
text: 'Hello',
completed: false
}
});
Якщо структура todo зміниться (наприклад, додасться поле createdAt), вам доведеться правити це в усіх місцях, де ви викликаєте dispatch.
Рішення: Action Creators
Action Creator — це просто функція, яка повертає об'єкт action.
import { ADD_TODO } from '../constants/actionTypes';
export const addTodo = (text) => {
return {
type: ADD_TODO,
payload: {
id: Date.now(),
text,
completed: false
}
};
};
Тепер в компоненті ви просто викликаєте функцію:
dispatch(addTodo('Hello'));
Структура файлів (Classic Pattern)
У "старому" Redux було прийнято сильно дробити файли. Типова структура папок виглядала так:
Переваги такого підходу:
- Розділення відповідальності: Кожен файл робить одну річ.
- Тестування: Легко тестувати action creators окремо від редюсерів.
Недоліки:
- Багато файлів: Щоб додати одну просту фічу, треба створити/відредагувати 3 файли (constant, action, reducer). Це називається Boilerplate code.
Саме цей "недолік" змушував багатьох ненавидіти Redux. Redux Toolkit вирішує це, але про нього пізніше.
Повний приклад з Action Creators
Давайте перепишемо наш лічильник, використовуючи ці патерни.
export const INCREMENT = 'INCREMENT';
export const DECREMENT = 'DECREMENT';
export const ADD_AMOUNT = 'ADD_AMOUNT';
import { INCREMENT, DECREMENT, ADD_AMOUNT } from './constants';
export const increment = () => ({ type: INCREMENT });
export const decrement = () => ({ type: DECREMENT });
export const addAmount = (amount) => ({
type: ADD_AMOUNT,
payload: amount
});
import { INCREMENT, DECREMENT, ADD_AMOUNT } from './constants';
const initialState = { value: 0 };
export function counterReducer(state = initialState, action) {
switch (action.type) {
case INCREMENT:
return { ...state, value: state.value + 1 };
case DECREMENT:
return { ...state, value: state.value - 1 };
case ADD_AMOUNT:
return { ...state, value: state.value + action.payload };
default:
return state;
}
}
Тепер наш код став більш структурованим, безпечним та професійним. У наступному розділі ми зануримося глибше в логіку Редюсерів.
Створення Store (Classic Redux)
Ми починаємо практичну частину з "класичного" Redux. Це саме той підхід, який використовувався до 2019 року. Хоча зараз рекомендовано використовувати Redux Toolkit, розуміння того, як працює createStore, є критичним для розуміння самої суті бібліотеки.
Логіка Reducers
Ми вже знаємо, що редюсер — це чиста функція (state, action) => newState. Але як писати складні редюсери, коли стан — це не просто одне число, а складний об'єкт?