Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Що таке чиста функція?». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Чиста функція (pure function)** завжди повертає однаковий результат для однакових аргументів і не має побічних ефектів. ```javascript const add = (a, b) => a + b; add(2, 3); // 5, завжди Date.now(); // інший результат кожного разу - нечиста ``` **Ключове:** `useMemo` і `React.memo` в React коректно пропускають зайві рендери тільки якщо функція чиста.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Чиста функція (pure function)** завжди повертає однаковий результат для однакових аргументів і не змінює нічого за межами свого тіла. ## Теорія ### Коротко - Чиста функція схожа на торговий автомат: ті самі монети, той самий товар, без бруду навколо - Дві умови: однакові аргументи завжди дають однаковий результат, і жодних побічних ефектів (мутацій, логів, API-запитів) - Результат залежить тільки від аргументів, не від глобального стану, DOM або `Date.now()` - `useMemo` і `React.memo` в React покладаються на чистоту функцій для безпечного пропуску зайвих рендерів ### Швидкий приклад ```javascript // Чиста: залежить тільки від аргументів 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 поверхово і пропускає рендер, якщо вхідні дані не змінились. Але це спрацює тільки якщо функція дійсно чиста. ### Типові помилки **Мутація аргументу** ```javascript // Виглядає нешкідливо, але ламає стан 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, породжує важко відтворювані баги зі застарілим станом. На код-рев'ю такий патерн трапляється частіше, ніж здається. Зазвичай він захований всередині функції, яка виглядає цілком безпечно. **Читання глобального стану** ```javascript let counter = 0; const getNextId = () => counter++; // різний результат кожного разу // Правильно: передай як аргумент const getNextId = (counter) => counter + 1; ``` Саме тому Redux забороняє цей патерн у редюсерах. **`console.log` всередині "чистої" функції** ```javascript 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:** Ні. Мутація є побічним ефектом за визначенням. Навіть якщо значення, що повертається, виглядає правильним, функція змінила щось поза своїм тілом. ## Приклади ### Базовий: розрахунок податку ```javascript // Чиста - безпечно обгортати в useMemo const calculateTax = (amount, rate) => amount * (1 + rate); calculateTax(100, 0.08); // 108 calculateTax(100, 0.08); // 108, завжди ``` Однакові вхідні дані, однаковий результат, жодних зовнішніх звернень. React може кешувати цей результат і не перераховувати, поки `amount` або `rate` справді не зміняться. ### Середній: фільтрація todos у компоненті ```javascript // Чиста функція фільтрації 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 для дорогих операцій зі списками.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.