Как работает Reconciliation?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как работает Reconciliation
Reconciliation (согласование) - это процесс, который React использует для определения того, какие части DOM нужно обновить. Это сердце React, которое делает его быстрым.
Проблема, которую решает Reconciliation
Когда состояние компонента меняется, React должен:
- Понять, что изменилось
- Найти соответствующие элементы DOM
- Обновить только нужные элементы
Наивный подход был бы обновить весь DOM, но это очень медленно:
// Плохо: переrendering весь список
function UserList({ users }) {
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
// Если добавить одного пользователя, React перерендер весь список
Как работает Reconciliation
Шаг 1: Virtual DOM Render фаза создает новое "виртуальное представление" UI:
const prevVDOM = {
type: 'div',
children: [{ type: 'p', text: 'Hello' }]
};
const nextVDOM = {
type: 'div',
children: [
{ type: 'p', text: 'Hello' },
{ type: 'p', text: 'World' } // новый элемент
]
};
Шаг 2: Diff алгоритм React сравнивает prevVDOM и nextVDOM:
// Было:
render() { return <div><Child a={1} /></div> }
// Стало:
render() { return <div><Child a={2} /></div> }
// React видит: props 'a' изменился с 1 на 2
// Нужно обновить Child компонент
Шаг 3: Commit фаза Render применяет изменения к реальному DOM:
// React обновляет только изменившиеся элементы
dom.textContent = 'Updated text'; // только это
// остальной DOM не трогает
Пример Reconciliation в действии
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
<StaticComponent />
</div>
);
}
function StaticComponent() {
return <div>This never changes</div>;
}
Когда нажимаем кнопку:
- setCount вызывает re-render
- React создает новый VDOM
- Сравнивает:
<p>Count: 0</p>-><p>Count: 1</p>(изменилось) - StaticComponent не изменился
- Обновляет только текст в <p>
Ключи (Keys) - критически важны
Когда у вас есть список элементов, ВСЕГДА используйте keys:
// Плохо: без keys
{users.map(user => (
<li>{user.name}</li>
))}
// Хорошо: с keys
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
Без keys React может спутать элементы:
// Было: [Alice, Bob, Charlie]
// Стало: [Alice, Bob, Charlie, Dave]
// Без keys React может подумать:
// - Alice -> Alice (ok)
// - Bob -> Bob (ok)
// - Charlie -> Charlie (ok)
// + Dave (новый)
// ВСЕ элементы пересчитываются
// С keys React знает:
// - key=1 (Alice) -> key=1 (Alice) (не меняется)
// - key=2 (Bob) -> key=2 (Bob) (не меняется)
// - key=3 (Charlie) -> key=3 (Charlie) (не меняется)
// + key=4 (Dave) (просто добавить)
// Обновляется только новый элемент
Reconciliation алгоритм (упрощенно)
Ract использует эвристики для быстрого сравнения:
// Эвристика 1: Разные типы элементов
if (prevElement.type !== nextElement.type) {
// Выкинуть старый элемент, создать новый
destroy(prevElement);
create(nextElement);
}
// Эвристика 2: Одинаковый тип, разные props
if (prevElement.props !== nextElement.props) {
// Обновить props
updateProps(element, nextElement.props);
}
// Эвристика 3: Одинаковые элементы
// Recurse на children с keys
Когда Reconciliation может быть неэффективным
Проблема: inline функции как children
// Плохо: новая функция на каждый render
<List items={items} onRender={(item) => <Item data={item} />} />
// Хорошо: одна функция
const renderItem = (item) => <Item data={item} />;
<List items={items} onRender={renderItem} />
Проблема: new object на каждый render
// Плохо:
const style = { color: 'red' }; // новый объект каждый раз!
<div style={style}></div>
// Хорошо:
const style = useMemo(() => ({ color: 'red' }), []);
<div style={style}></div>
Оптимизация Reconciliation
React.memo - мемоизирует компонент:
const Item = React.memo(({ data }) => {
return <div>{data.name}</div>;
});
// Item не будет переrenderен, если props не изменились
useMemo - мемоизирует вычисления:
const expensiveValue = useMemo(
() => computeExpensive(data),
[data] // зависимости
);
useCallback - мемоизирует функции:
const handleClick = useCallback(() => {
setCount(count + 1);
}, [count]);
// Функция не создается заново, если count не изменился
Reconciliation vs Concurrent Rendering
В React 18+ reconciliation может быть прерван для более приоритетных обновлений:
// Высокий приоритет: user input
flushSync(() => setInputValue(e.target.value));
// Низкий приоритет: фоновое обновление
startTransition(() => {
setSearchResults(data);
});
Заключение
Reconciliation - это магия React, которая:
- Быстро определяет, что изменилось
- Минимизирует DOM обновления
- Использует эвристики для эффективного сравнения
- Позволяет разработчикам писать код как будто компонент полностью переrenderится
- На самом деле обновляет только нужные части
Понимание Reconciliation помогает писать более производительный React код и избегать типичных ошибок.