У контексті JS, React, Redux та функціонального програмування часто використовуються такі терміни як
- мутації / мутабельність (mutations / mutability),
- побічні ефекти (side effects),
- чисті функції (pure functions),
- ідемпотентні функції (idempotent functions).
Давайте з’ясуємо, що вони означають, як використовуються (в контексті JS), та, нарешті, поставимо крапку в термінологічній плитунині, яка притаманна цим термінам.
Мутації

Мутація в програмуванні означає зміну стану об’єкта, змінної чи структури даних після їх створення. І нажаль ніяк не пов’язана з черепашками-ніндзя, або з людьми-мутантами.
У JavaScript примітивні значення (всі типи данних крім обʼєктів) є іммутабельними (immutable), тобто вони не можуть бути змінені, але втім, можуть бути переназначені.
Часто можна почути, що let
є мутабельним, а const
— ні. Це не зовсім правильно.
Різниця між let
та const
саме в тому, що const
не можна переназначити, але так само як і для let
, обʼекти і массиви в const
не є іммутабельними.
Приклад
const person = {name: 'Cassius Marcellus Clay'};
person.name = 'Muhammad Ali';
У цьому прикладі властивість name
об’єкта person
змінюється. Це є мутацією.
Чому це вважається поганим?
Мутації самі по собі не є шкідливими, але вони можуть призводити до помилок і ускладнювати розуміння коду.
Короткий приклад:
const a = [1, 2, 3];
const b = a;
b.push(4);
console.log(a); // [1, 2, 3, 4]
У цьому прикладі ми мутуємо масив b
, але він вказує на той самий об’єкт, що і a
. Це може призвести до непередбачуваної поведінки.
Щоб уникнути цього, можна використовувати методи, які повертають новий об’єкт, а не мутують старий:
const a = [1, 2, 3];
const b = {...a, 4};
console.log(a); // [1, 2, 3]
Іммутабельність в React/Redux
React
Одне з основних правил React - це те що props
і state
повинні бути іммутабельними.
Redux
Так само і в Redux - перше правило в Redux Style Guide - не мутувати стан.
Бібліотеки для роботи з іммутабельністю
Для того щоб уникнути мутацій, можна використовувати вбудований в JavaScript метод Object.freeze
або бібліотеки, які допомагають працювати з іммутабельністю, наприклад:
Побічні ефекти (Side Effects)

Побічний ефект — це будь-яка зміна в системі, яка впилває на зовнішній світ.
Можливо, зараз це звучить дещо абстрактно.
Коли ви викликаєте функцію, ваш комп’ютер працює інтенсивніше, виділяючи тепло. Це тепло може поступово підвищувати температуру в кімнаті. Чи можна це вважати побічним ефектом функції?
Що ж, це залежить від контексту:
В контексті фізики — так. Але в контексті программи, яку ми розробляємо, — ні. Зазвичай побічні ефекти в програмуванні стосуються лише того, що відбувається в межах программи, наприклад:
- Зміна глобальної змінної.
let counter = 0;
function increment() {
counter++; // Побічний ефект: змінюється зовнішній стан
}
increment();
console.log(counter); // 1
- Логування в консоль.
function logMessage(message) {
console.log(message); // Побічний ефект: вивід у консоль
}
logMessage('Hello, world!');
Логування не змінює стан програми, але впливає на середовище виконання, залишаючи слід у консолі.
- Зміни в DOM.
function updateTitle(newTitle) {
document.title = newTitle; // Побічний ефект: зміна заголовка сторінки
}
updateTitle('Новий заголовок');
console.log(document.title); // "Новий заголовок"
Побічні ефекти в React/Redux
React
В функціональних компонентах React місцем для побічних ефектів є хук useEffect
. Там можуть відбуватися такі побічні ефекти як:
- Запити до сервера
useEffect(() => {
fetch('https://api.example.com/data')
.then((response) => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then((data) => setData(data))
.catch((error) => console.error('Fetch error:', error));
}, []);
Варто зазначити, що для запитів до сервера можна використовувати бібліотеки, які дозволяють писати код більш декларативно, наприклад React Query або SWR.
-
Підписка на події
useEffect(() => { const handleOffline = () => setIsOffline(true); const handleOnline = () => setIsOffline(false); window.addEventListener('offline', handleOffline); window.addEventListener('online', handleOnline); return () => { window.removeEventListener('offline', handleOffline); window.removeEventListener('online', handleOnline); }; }, []);
-
Робота з локальним сховищем
useEffect(() => { const data = JSON.parse(localStorage.getItem('data')); setData(data); }, []);
-
Маніпуляції з DOM
useEffect(() => { document.title = 'Новий заголовок'; }, []);
Redux
В Redux управління побічними ефектами у ньому реалізується через middleware. Найпопулярнішим middleware для цього — Redux Thunk
const fetchUserById = createAsyncThunk('users/fetchByIdStatus', async (userId: number, thunkAPI) => {
const response = await userAPI.fetchById(userId);
return response.data;
});
Чисті функції (Pure Functions)
Чисті функції — це функції, які мають дві властивості:
- Вони не мають побічних ефектів.
- Детермінізм - це значить, що для одних і тих самих вхідних даних функція завжди повертає один і той самий результат.
Давайте розглянемо пару прикладі
Приклади
Чи буде ця функція чистою?
function addRandom(a) {
return a + Math.random();
}
Для цього нам потрібно відповісти на два питання:
- Чи має ця функція побічні ефекти?
Ні, побічних ефектів тут немає.
- Чи є ця функція детермінованою? Тобто, чи завжди повертає один і той самий результат для одних і тих самих вхідних даних?
Ні, ця функція не є детермінованою, оскільки Math.random()
повертає випадкове число. Через що при першому виклику addRandom(2)
може повернути, наприклад, 2.123
, а при другому, скажімо, — 2.456
.
Отже, ця функція не є чистою.
А ця?
function add(a, b) {
return a + b;
}
Ця функція відповідає обом вимогам чистої функції: вона не має побічних ефектів і є детермінованою.
Чисті функції в Redux
Одним з трьох основних принципов в Redux є використання чистих функцій
Ідемпотентні функції


Термін «ідемпотентність» означає властивість, яка проявляється в тому, що повторне застосування операції над об’єктом не змінює його стан після першого виконання.
Функція є ідемподентною, якщо її повторні виклики з однаковими вхідними даними не змінюють результат або стан після першого виклику.
Тобто вликлик функції f(x)
декілька разів, наприклад f(x); f(x); f(x);
дасть той самий результат, що і один виклик f(x);
.
(Тут варто зазначити, що в математиці ідемподентність означає, що f(f(x)) = f(x)
, але то вже трохи інша історія. Якщо цікаво, можете прочитати це в чудовій книзі Кайла Сімпсона Functional-Light JavaScript)
Приклади
function convertToUpper(str) {
return str.toUpperCase();
}
Ця функція є ідемпотентною, тому що вилкик цієї функції, скажімо, 10 разів, дає той же самий еффект, що й виклик один раз. Ця функція також є й чистою. Чисті функції завжди є ідемпотентними, але не обов’язково навпаки.
function setUserActive(user) {
user.status = 'active';
}
Ця функція є ідемпотентною, оскільки при кожному виклику з однаковим об’єктом user
буде встановлювати статус active
.
Але ця функція не є чистою, оскільки мутує об’єкт user
.
function addHiddenClass(element) {
element.classList.add('hidden');
}
Аналогічно, функція addHiddenClass є ідемпотентною: після першого виклику клас hidden додається до елемента, і наступні виклики не змінюють стан. Однак через те, що функція мутує DOM, вона не є чистою.
Тут у нас не буде прикладів використання в React/Redux, тому що частіше термін ідемпотентність використовується в контексті API або HTTP методів.
Висновок
На цьому все. Сподіваюсь ця стаття додала трошки ясності в вищезгадані терміни. Якщо у вас виникли питання, або ви знайшли помилку, можете написати тут в коментарях або на будь-який з контактів зазначенних в футері.