Skip to main content
Практика завдань

Реконсиляція в React

Що таке Реконциляція?

Реконциляція — це алгоритм, який React використовує для порівняння двох дерев елементів та визначення, які частини реального DOM потрібно оновити.

Коли стан або пропси компонент змінюються, React створює нове дерево елементів і порівнює його з попереднім, щоб внести мінімальні зміни в DOM.


Чому це потрібно?

Оновлення DOM — це витратна операція. React оптимізує цей процес:

  1. Створює віртуальну копію DOM (Virtual DOM)
  2. Створює нове дерево, коли відбуваються зміни
  3. Порівнює старе та нове дерева (diffing)
  4. Оновлює лише змінені частини реального DOM

Як працює алгоритм

Основні правила

React використовує евристичний алгоритм O(n) замість O(n³). Він базується на двох припущеннях:

  1. Елементи різних типів створюють різні дерева
  2. Розробник може підказати, які елементи стабільні, використовуючи ключі

Порівняння елементів різних типів

Коли кореневі елементи мають різні типи, React видаляє старе дерево і будує нове з нуля.

javascript
// Старе дерево <div> <Counter /> </div> // Нове дерево <span> <Counter /> </span>

React:

  1. Видаляє <div> та всіх дітей
  2. Відмонтовує Counter (викликає componentWillUnmount)
  3. Створює новий <span>
  4. Монтує новий Counter (викликає componentDidMount)

Порівняння елементів одного типу

DOM Елементи

Коли типи збігаються, React порівнює атрибути та оновлює лише змінені:

javascript
// Старий <div className="before" title="old" /> // Новий <div className="after" title="old" />

React оновить лише className, title залишиться незмінним.

Стилі

javascript
// Старий <div style={{color: 'red', fontWeight: 'bold'}} /> // Новий <div style={{color: 'blue', fontWeight: 'bold'}} />

React оновлює лише color.


Порівняння компонентів одного типу

Коли компонент оновлюється, екземпляр залишається тим самим, тому стан зберігається:

javascript
<Counter count={1} /> // Після оновлення <Counter count={2} />

React:

  1. Оновлює пропси компонент
  2. Викликає componentWillReceiveProps() та componentWillUpdate()
  3. Викликає render()
  4. Виконує реконциляцію на результаті рендерингу

Рекурсивна обробка дітей

React рекурсивно обробляє дітей. Розглянемо приклад:

Додавання елемента в кінець

javascript
// Старий <ul> <li>first</li> <li>second</li> </ul> // Новий <ul> <li>first</li> <li>second</li> <li>third</li> </ul>

React:

  1. Порівнює перший <li> — ідентичні, нічого не робить
  2. Порівнює другий <li> — ідентичні, нічого не робить
  3. Додає третій <li>

Ефективно!

Додавання елемента на початок (без ключа)

javascript
// Старий <ul> <li>first</li> <li>second</li> </ul> // Новий <ul> <li>zero</li> <li>first</li> <li>second</li> </ul>

React:

  1. Порівнює перший <li> — "first" ≠ "zero", оновлює
  2. Порівнює другий <li> — "second" ≠ "first", оновлює
  3. Додає третій <li>

Неефективно! React не зрозумів, що елементи просто зрушилися.


Ключі

key допомагає React зрозуміти, які елементи змінилися, були додані або видалені.

З ключами

javascript
// Старий <ul> <li key="first">first</li> <li key="second">second</li> </ul> // Новий <ul> <li key="zero">zero</li> <li key="first">first</li> <li key="second">second</li> </ul>

React:

  1. Бачить новий ключ "zero" — створює елемент
  2. Бачить ключ "first" — елемент існував, зберігає його
  3. Бачить ключ "second" — елемент існував, зберігає його

Ефективно!


Правила використання ключів

Унікальність серед братів

Ключі повинні бути унікальними лише серед братніх елементів:

javascript
function Blog(props) { const sidebar = ( <ul> {props.posts.map(post => <li key={post.id}>{post.title}</li> )} </ul> ); const content = props.posts.map(post => <div key={post.id}> <h3>{post.title}</h3> <p>{post.content}</p> </div> ); return ( <div> {sidebar} <hr /> {content} </div> ); }

Ті ж ключі в різних масивах — це нормально.

Стабільність ключів

Ключі повинні бути стабільними та передбачуваними. Не використовуйте випадкові значення:

javascript
// Погано {items.map(item => <Item key={Math.random()} data={item} />)} // Погано (стан буде втрачено при перестановці) {items.map((item, index) => <Item key={index} data={item} />)} // Добре {items.map(item => <Item key={item.id} data={item} />)}

Чому індекс — погана ідея?

javascript
function TodoList() { const [todos, setTodos] = useState([ { id: 1, text: 'Купити молоко', done: false }, { id: 2, text: 'Прибрати кімнату', done: true } ]); // Погано return ( <ul> {todos.map((todo, index) => ( <TodoItem key={index} todo={todo} /> ))} </ul> ); }

Проблема: коли видаляється перший елемент, індекси зсуваються, і React вважає, що зміст змінився, а не порядок.

javascript
// Добре return ( <ul> {todos.map(todo => ( <TodoItem key={todo.id} todo={todo} /> ))} </ul> );

Оптимізації

shouldComponentUpdate

Ви можете оптимізувати реконциляцію, повідомляючи React, коли компонент НЕ ПОТРІБНО переробляти:

javascript
class MyComponent extends React.Component { shouldComponentUpdate(nextProps, nextState) { // Рендерити лише якщо id змінився return this.props.id !== nextProps.id; } render() { return <div>{this.props.data}</div>; } }

React.PureComponent

Автоматично виконує поверхневе порівняння пропсів та стану:

javascript
class MyComponent extends React.PureComponent { render() { return <div>{this.props.data}</div>; } }

React.memo

Для функціональних компонентів:

javascript
const MyComponent = React.memo(function MyComponent(props) { return <div>{props.data}</div>; }); // З кастомним порівнянням const MyComponent = React.memo( function MyComponent(props) { return <div>{props.data}</div>; }, (prevProps, nextProps) => { return prevProps.id === nextProps.id; } );

Практичні приклади

Список з фільтрацією

javascript
function UserList({ users, filter }) { const filteredUsers = users.filter(user => user.name.includes(filter) ); return ( <ul> {filteredUsers.map(user => ( <li key={user.id}> {user.name} <input type="text" /> </li> ))} </ul> ); }

Якщо використовувати index замість user.id, значення введення змішаються при зміні фільтра, оскільки React не зрозуміє, що це різні користувачі.

Перемикання між компонентми

javascript
function App() { const [tab, setTab] = useState('profile'); return ( <div> {tab === 'profile' && <Profile />} {tab === 'settings' && <Settings />} </div> ); }

При перемиканні вкладок React повністю демонтує один компонент і монтує інший, оскільки це різні типи.

Щоб зберегти стан, використовуйте display: none:

javascript
function App() { const [tab, setTab] = useState('profile'); return ( <div> <div style={{ display: tab === 'profile' ? 'block' : 'none' }}> <Profile /> </div> <div style={{ display: tab === 'settings' ? 'block' : 'none' }}> <Settings /> </div> </div> ); }

Загальні помилки

Зміна типу елемента

javascript
function Component({ isLoading }) { if (isLoading) { return <div>Завантаження...</div>; } return <span>Дані</span>; }

Коли isLoading змінюється, React демонтує весь компонент, оскільки divspan.

Рішення: використовуйте той самий тип:

javascript
function Component({ isLoading }) { return <div>{isLoading ? 'Завантаження...' : 'Дані'}</div>; }

Відсутні ключі в списках

javascript
// Погано function List({ items }) { return ( <ul> {items.map(item => <li>{item.name}</li>)} </ul> ); }

React за замовчуванням використовуватиме порядкові номери, що призведе до проблем, коли порядок змінюється.

Нестабільні ключі

javascript
// Погано function List({ items }) { return ( <ul> {items.map(item => ( <li key={`${item.name}-${Date.now()}`}> {item.name} </li> ))} </ul> ); }

Ключі змінюються при кожному рендері, React буде перетворювати елементи.


Висновок

Реконциляція:

  • Алгоритм для порівняння дерев елементів
  • Працює в O(n) завдяки евристикам
  • Різні типи елементів = повна перебудова
  • Одні й ті ж типи = оновлення атрибутів
  • Ключі допомагають ідентифікувати елементи в списках
  • Можна оптимізувати за допомогою shouldComponentUpdate, PureComponent, memo

На співбесідах:

Важливо вміти:

  • Пояснити, що таке реконциляція і чому вона потрібна
  • Описати, як React порівнює елементи різних і одного типу
  • Пояснити важливість ключів у списках
  • Навести приклади, коли індекс як ключ є поганим
  • Описати методи оптимізації (PureComponent, memo)

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?
Практика завдань