Що таке чиста функція?
Чиста функція (pure function) завжди повертає однаковий результат для однакових аргументів і не змінює нічого за межами свого тіла.
Теорія
Коротко
- Чиста функція схожа на торговий автомат: ті самі монети, той самий товар, без бруду навколо
- Дві умови: однакові аргументи завжди дають однаковий результат, і жодних побічних ефектів (мутацій, логів, API-запитів)
- Результат залежить тільки від аргументів, не від глобального стану, DOM або
Date.now() useMemoіReact.memoв React покладаються на чистоту функцій для безпечного пропуску зайвих рендерів
Швидкий приклад
// Чиста: залежить тільки від аргументів
const add = (a, b) => a + b;
add(2, 3); // 5, завжди
// Нечиста: читає зовнішній стан
const now = () => Date.now(); // інший результат кожного разу
// Нечиста: мутує зовнішню змінну
let count = 0;
const increment = () => count++; // побічний ефектadd торкається тільки своїх аргументів. Date.now() читає годинник, зовнішній стан поза її контролем. increment змінює count за межами свого тіла. Останні дві нечисті.
Головна різниця
Чиста функція залежить тільки від переданих аргументів. Вона не читає глобальні змінні, не чіпає DOM і не звертається ні до чого поза своїм тілом. Це робить результат передбачуваним: викличи add(2, 3) тисячу разів, і завжди отримаєш 5. Нечиста функція порушує цей контракт, як тільки читає або змінює щось зовнішнє.
Коли використовувати
- Математика або логіка над props/станом (розрахунок суми кошика, фільтрація списку): потрібна чиста функція
- Обгортання в
useMemoабоReact.memoдля уникнення зайвих рендерів: обов'язково чиста функція - API-запити, DOM-мутації, таймери: нечисті за природою, ізолюй в
useEffect - Загальні утилітарні хелпери між компонентами: чисті, бо їх легко тестувати і перевикористовувати
Під капотом
V8 не перевіряє чистоту явно. Він виводить передбачуваність із відсутності зовнішніх мутацій під час JIT-компіляції, що відкриває шлях до оптимізацій на кшталт inline caching. React робить схоже на вищому рівні: порівнює props поверхово і пропускає рендер, якщо вхідні дані не змінились. Але це спрацює тільки якщо функція дійсно чиста.
Типові помилки
Мутація аргументу
// Виглядає нешкідливо, але ламає стан React
const badSum = (numbers) => {
numbers.push(0); // мутує оригінальний масив!
return numbers.reduce((a, b) => a + b, 0);
};
// Правильно
const goodSum = (numbers) => {
return [...numbers].reduce((a, b) => a + b, 0);
};React очікує на незмінність даних. Мутація масиву, що прийшов як prop, породжує важко відтворювані баги зі застарілим станом. На код-рев'ю такий патерн трапляється частіше, ніж здається. Зазвичай він захований всередині функції, яка виглядає цілком безпечно.
Читання глобального стану
let counter = 0;
const getNextId = () => counter++; // різний результат кожного разу
// Правильно: передай як аргумент
const getNextId = (counter) => counter + 1;Саме тому Redux забороняє цей патерн у редюсерах.
console.log всередині "чистої" функції
const multiply = (a, b) => {
console.log('computing'); // побічний ефект
return a * b;
};Лог не змінює значення, що повертається, але змінює спостережувану поведінку програми. React не може безпечно мемоізувати таку функцію.
Де зустрічається
- React useMemo: обгортай дорогі чисті обчислення (фільтрація великих списків), щоб React не перезапускав їх при кожному рендері
- Redux reducers: мусять бути чистими, приймають стан і action, повертають новий стан без мутацій (Redux Toolkit використовує Immer для зручності)
- Lodash FP / Ramda: всі функції чисті за дизайном, безпечно компонуються в пайплайни
Питання на співбесіді
Q: Що таке referential transparency?
A: Виклик функції можна замінити її результатом без зміни поведінки програми. add(2, 3) і 5 взаємозамінні будь-де в коді.
Q: Чому React важливо, чи є функція чистою?
A: React.memo і useMemo покладаються на те, що однакові вхідні дані дають однаковий результат. Якщо функція нечиста, кешування її результату призведе до неправильного стану UI.
Q: Чи робить useCallback callback чистим?
A: Ні. useCallback мемоізує посилання на функцію, щоб воно не мінялось між рендерами. Але якщо callback читає або мутує зовнішній стан, він залишається нечистим. Стабільність посилання і чистота функції: це різні речі.
Q: Чи може функція, що мутує аргумент, бути чистою?
A: Ні. Мутація є побічним ефектом за визначенням. Навіть якщо значення, що повертається, виглядає правильним, функція змінила щось поза своїм тілом.
Приклади
Базовий: розрахунок податку
// Чиста - безпечно обгортати в useMemo
const calculateTax = (amount, rate) => amount * (1 + rate);
calculateTax(100, 0.08); // 108
calculateTax(100, 0.08); // 108, завждиОднакові вхідні дані, однаковий результат, жодних зовнішніх звернень. React може кешувати цей результат і не перераховувати, поки amount або rate справді не зміняться.
Середній: фільтрація todos у компоненті
// Чиста функція фільтрації
const filterTodos = (todos, filter) => {
return todos.filter(todo => {
if (filter === 'active') return !todo.completed;
if (filter === 'completed') return todo.completed;
return true;
});
};
// У компоненті
const visibleTodos = useMemo(
() => filterTodos(todos, filter),
[todos, filter]
);filterTodos читає тільки свої аргументи. Обгортання в useMemo означає: React перезапускає фільтр тільки коли todos або filter справді змінились, а не при кожному рендері. Цей патерн є в документації самого React для дорогих операцій зі списками.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.