Що таке "derived state"
Що таке derived state (похідний стан)
Derived state - це стан, який можна обчислити з уже наявного стану або пропсів.
Простіше кажучи: це дублікат даних, який не є "джерелом істини" (source of truth), а залежить від інших даних.
Приклад - derived state у чистому вигляді (погано)
function Example({ items }) {
const [count, setCount] = useState(items.length); // derived state
return <p>Елементів: {count}</p>;
}Тут count повністю залежить від items.
Якщо items зміняться, count залишиться старим,
доки ти не оновиш його вручну через useEffect або setCount.
Це створює несинхронність -
у тебе з'являються два джерела істини (items і count).
Чому derived state небезпечний
1. Порушує принцип "Single Source of Truth"
React-архітектура будується на ідеї:
"Кожен стан повинен мати одне джерело істини."
Коли ти створюєш derived state, ти фактично робиш "копію" цих даних. Тепер потрібно стежити, щоб обидві версії завжди були синхронізовані. Це шлях до багів.
2. Несинхронні оновлення
function Example({ items }) {
const [count, setCount] = useState(items.length);
useEffect(() => {
setCount(items.length);
}, [items]);
return <p>{count}</p>;
}Так, можна "синхронізувати" через useEffect,
але це зайвий рендер і зайва складність.
Навіщо зберігати count, якщо його можна обчислити напряму?
Правильно:
function Example({ items }) {
return <p>Елементів: {items.length}</p>;
}3. Похідні дані можуть "відставати"
Якщо ти обчислюєш derived state всередині setState,
він може використовувати застарілі дані,
оскільки setState асинхронний:
setItems([...items, newItem]);
setCount(items.length); // ще старе значенняReact застосує обидва апдейти пізніше,
і count виявиться на 1 менше, ніж потрібно.
Що робити замість derived state
Замість зберігання похідних даних - обчислюй їх на льоту, коли потрібно відмалювати.
Приклад 1 - обчислення прямо в JSX
function Example({ items }) {
const total = items.length; // обчислюємо без зберігання
return <p>Всього елементів: {total}</p>;
}Приклад 2 - обчислення всередині useMemo (для важких обчислень)
Якщо обчислення дороге (наприклад, фільтрація, сортування),
використовуй useMemo, щоб кешувати результат:
function Products({ items, filter }) {
const visibleItems = useMemo(
() => items.filter(item => item.category === filter),
[items, filter]
);
return <p>Показано: {visibleItems.length}</p>;
}Тут visibleItems - derived value,
але не stored state, а просто обчислюване значення - тому безпечно.
Коли derived state все-таки допустимий
Іноді похідний стан виправданий, якщо:
- Він не прямо обчислюється з props/state, а зберігає проміжне значення (наприклад, користувач вводить, а ми дебаунсимо).
- Або тобі потрібно "заморозити" значення на момент події.
Приклади допустимих випадків:
1. Контрольований ввід (debounce)
const [query, setQuery] = useState("");
const [debouncedQuery, setDebouncedQuery] = useState(query);
useEffect(() => {
const id = setTimeout(() => setDebouncedQuery(query), 500);
return () => clearTimeout(id);
}, [query]);→ debouncedQuery - derived, але свідомо і з ефектом.
2. "Знімок" даних
const [snapshot, setSnapshot] = useState(null);
function handleSubmit() {
setSnapshot(formData); // зберігаємо стан форми на момент відправлення
}→ Це не дублювання, а фіксація значення в часі.
Підсумок
| Питання | Відповідь |
|---|---|
| Що таке derived state? | Стан, обчислюваний з іншого стану або пропсів |
| Чому це погано? | Створює дублювання даних і розсинхронізацію |
| Як краще? | Обчислювати на льоту або через useMemo |
| Коли допустимо? | Для "знімків" даних, дебаунсу, проміжних станів |
| Головний принцип | Завжди тримай "джерело істини" в одному місці |
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.