Чисті функції та Іммутабельність
Чисті функції та Іммутабельність
Це, мабуть, технічно найважливіший розділ для розуміння того, як писати Redux код правильно. Більшість багів у початківців виникають саме через порушення цих правил.
Чисті Функції (Pure Functions)
Концепція прийшла з функціонального програмування. Функція вважається "чистою", якщо вона відповідає двом критеріям:
- Детермінованість: Для одних і тих самих вхідних аргументів вона завжди повертає однаковий результат.
- Відсутність побічних ефектів (No Side Effects): Вона не змінює нічого за своїми межами (глобальні змінні, стан DOM, мережеві запити тощо) і не змінює свої аргументи.
Приклади
// Чиста функція
// Завжди повертає a + b.
// Не змінює нічого зовні.
function add(a, b) {
return a + b;
}
// Нечиста (Недетермінована)
// Повертає різні значення при кожному виклику.
function randomId() {
return Math.random().toString(36);
}
// Нечиста (Побічний ефект)
// Змінює глобальну змінну count.
let count = 0;
function increment() {
count++; // Side effect!
return count;
}
// Нечиста (Мережевий запит)
// Залежить від зовнішнього світу.
async function getUser() {
return await fetch('/api/user');
}
Це означає, що в редюсері ЗАБОРОНЕНО:
- Робити
Math.random(),Date.now(). - Робити API запити (
fetch,axios). - Змінювати змінні за межами редюсера.
- Мутувати (змінювати) аргументи
stateабоaction.
Іммутабельність (Immutability)
Іммутабельність означає, що дані не можна змінювати після створення. Якщо ви хочете змінити дані, ви повинні створити копію з новими значеннями.
У JavaScript об'єкти та масиви є мутабельними (змінними) за посиланням. Це корінь зла в Redux, якщо не бути обережним.
Мутація vs Іммутабельність
const user = { name: 'Ivan', age: 25 };
// ❌ МУТАЦІЯ
// Ми змінили існуючий об'єкт.
// Посилання на об'єкт залишилося тим самим!
user.age = 26;
// ✅ ІММУТАБЕЛЬНЕ ОНОВЛЕННЯ
// Ми створили НОВИЙ об'єкт.
// Посилання змінилося!
const updatedUser = { ...user, age: 26 };
Чому Redux вимагає іммутабельності?
- Продуктивність (Reference Equality Check):
Redux перевіряє, чи змінився стан, порівнюючи посилання (
prevProps === nextProps).- Якщо ми зробимо мутацію (
obj.prop = 1), посилання наobjне зміниться. React/Redux подумає, що нічого не змінилося, і не оновить компонент. - Якщо ми створимо новий об'єкт, посилання буде новим, і Redux зрозуміє: "Ага, дані змінилися, треба перемалювати UI".
- Якщо ми зробимо мутацію (
- Передбачуваність та Time Travel: Щоб "відмотати час назад", Redux DevTools зберігає копії попередніх станів. Якщо ви мутуєте стан, ви перезаписуєте історію. Повернутися назад стає неможливо.
Патерни Іммутабельного Оновлення
Вам доведеться часто писати такий код у класичному Redux (або використовувати Redux Toolkit, який робить це за вас).
1. Оновлення об'єкта
Використовуйте Spread Operator (...).
const state = {
loading: false,
data: { id: 1, text: 'Hello' }
};
// ❌ Погано
state.loading = true;
// ✅ Добре
return {
...state, // Копіюємо всі поля (data)
loading: true // Перезаписуємо loading
};
2. Оновлення вкладеного об'єкта
Це найскладніше. Потрібно копіювати кожен рівень вкладеності (Shallow Copy).
// Хочемо змінити state.data.text
return {
...state,
data: {
...state.data, // Копіюємо поля всередині data
text: 'Bye' // Оновлюємо text
}
};
3. Додавання в масив
const todos = [1, 2, 3];
// ❌ Погано (push мутує масив)
todos.push(4);
// ✅ Добре
const newTodos = [...todos, 4];
4. Видалення з масиву
Використовуйте filter, бо він повертає новий масив.
// Видалити todo з id=2
const newTodos = state.todos.filter(todo => todo.id !== 2);
5. Оновлення елемента в масиві
Використовуйте map.
// Перемкнути статус todo з id=2
const newTodos = state.todos.map(todo => {
if (todo.id === 2) {
// Знайшли потрібний? Повертаємо копію зі змінами
return { ...todo, completed: !todo.completed };
}
// Не той? Повертаємо без змін
return todo;
});
Тепер, коли ми озброєні теорією, давайте напишемо наш перший реальний Redux код.
Філософія Redux та Три Принципи
Redux часто вважають складною бібліотекою, але насправді він базується на кількох дуже простих ідеях. Складність виникає не з самої бібліотеки, а з нових термінів та обмежень, які вона накладає.
Створення Store (Classic Redux)
Ми починаємо практичну частину з "класичного" Redux. Це саме той підхід, який використовувався до 2019 року. Хоча зараз рекомендовано використовувати Redux Toolkit, розуміння того, як працює createStore, є критичним для розуміння самої суті бібліотеки.