Як React визначає, які компоненти потрібно оновити?
Ключова ідея
React не оновлює весь UI, а лише ті компоненти, у яких змінилися props, state або контекст.
Щоб зрозуміти це, React використовує свій алгоритм узгодження (reconciliation algorithm).
1. Тригер оновлення
Повторний рендер (а отже, потенційне оновлення компонента) може бути викликаний:
- Зміною внутрішнього стану (useState, setState)
- Зміною props, переданих від батьківського компонента
- Зміною контексту (useContext)
- Явним викликом forceUpdate() (у класових компонентах)
Ці події запускають render phase - React знову викликає функцію-компонент (або render() у класі).
2. React будує нове дерево Virtual DOM
React викликає всі задіяні компоненти і створює нове «віртуальне представлення» UI, тобто нове дерево Virtual DOM.
Кожен JSX-елемент перетворюється на об'єкт на кшталт:
{
type: 'div',
key: null,
props: { className: 'btn', children: 'Click me' }
}3. React порівнює старе і нове дерево
Тепер починається reconciliation (узгодження):
- React бере старе дерево Virtual DOM (до оновлення).
- Порівнює його з новим деревом, створеним після виклику render().
- Обчислює, які вузли змінилися - це процес diffing.
4. Правила, за якими React вирішує, що оновити
Правило 1: порівняння за типом елемента
Якщо тип (type) елемента такий самий, React зберігає старий DOM-вузол і оновлює лише пропси.
<div className="old" />
<div className="new" /> // same elementReact просто оновить className, не перестворюючи елемент.
Якщо тип змінився, React видаляє старий вузол і створює новий.
<div>...</div>
<span>...</span>Старий <div> буде видалено, <span> - створено заново.
Правило 2: порівняння за key у списках
Коли в тебе список компонентів:
{items.map(item => <Todo key={item.id} text={item.text} />)}React використовує key, щоб зрозуміти, які елементи залишилися незмінними, а які нові / видалені / переміщені.
- Якщо key збігається, елемент залишається.
- Якщо key інший, React вважає його новим елементом.
Це дозволяє не перестворювати весь список, а лише змінені елементи.
Правило 3: порівняння пропсів і стану
Якщо компонент того самого типу, але:
- props змінилися (за значенням або посиланням),
- або внутрішній state оновився,
React перемалює (re-render) цей компонент, щоб перерахувати Virtual DOM.
Якщо пропси не змінилися (і компонент не залежить від контексту), React може пропустити його перерендер (особливо за використання React.memo).
Правило 4: використання shouldComponentUpdate / React.memo
Для оптимізації React може запитати в компонента:
«Чи потрібно тобі перерендеритися?»
- У класових компонентах - через shouldComponentUpdate(nextProps, nextState).
- У функціональних - через React.memo(Component, areEqual).
Якщо відповідь - «Ні, все те саме», React пропускає оновлення і не викликає render().
5. React обчислює diff і оновлює лише змінені частини DOM
Після порівняння React знає:
- які компоненти мають оновитися;
- які DOM-вузли потрібно перемалювати;
- які залишити як є.
Далі настає commit phase, де React:
- застосовує ці мінімальні зміни до реального DOM;
- викликає useLayoutEffect і useEffect.
Ілюстрація процесу
State changed
↓
Component recreated the Virtual DOM
↓
React compared the old and new tree (reconciliation)
↓
Found the changed nodes
↓
Applied only the necessary changes to the DOM (commit phase)Приклад
function App() {
const [count, setCount] = useState(0);
return (
<div>
<Header />
<Counter value={count} />
<button onClick={() => setCount(c => c + 1)}>+</button>
</div>
);
}- Змінився count, тому React викликає App() знову.
- Створюється нове Virtual DOM.
- React порівнює:
<Header />- ті самі пропси, не оновлюється.<Counter value={count} />- змінилися пропси, оновлюється.
- Оновлюється лише текст усередині
<Counter>, усе інше залишається незмінним.
Підсумок
React визначає, які компоненти потрібно оновити, за допомогою reconciliation algorithm: порівнюючи нове і старе дерево Virtual DOM та аналізуючи тип, ключ, props і state.
| Що змінилося | React оновить? | Чому |
|---|---|---|
| state усередині компонента | Так | компонент перестворюється |
| props від батьківського компонента | Так | нові значення пропсів |
| Батьківський компонент, але не props дитини | Залежить | якщо не React.memo |
| context | Так | нове значення контексту |
| key елемента | Так | створюється новий елемент |
| Нічого не змінилося | Ні | diff не знайдено |
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.