Ось готовий урок, створений спеціально для тебе у стилі Девіда Малана (CS50).
🎓 CS50: React. Оптимізація ререндерів і React.memo
Привіт, світе! Радий бачити вас знову.
Сьогодні ми зазирнемо «під капот» React і розберемо тему, яка відрізняє простого кодера від інженера, що піклується про продуктивність. Ми говоримо про оптимізацію ререндерів.
1. 🔥 Вступ: Чому ваш додаток «гальмує»?
Уявіть, що ви — художник. Я прошу вас намалювати портрет сім'ї: тато, мама і дитина. Ви витрачаєте години, малюєте кожну деталь. Чудово!
Але раптом тато вирішує змінити краватку з червоної на синю. Ви, як сумлінний художник (або як React за замовчуванням), берете і... викидаєте стару картину і малюєте ВСЮ сім'ю з нуля. Маму, яка не змінилася. Дитину, яка сидить так само. І тата з новою краваткою.
Звучить абсурдно, чи не так? Навіщо перемальовувати маму, якщо змінився тільки тато?
У світі React це відбувається постійно. Коли батьківський компонент оновлюється, всі його діти оновлюються теж. Автоматично. Завжди.
Чому це проблема? 1. Батарея телефону: Користувач заходить на ваш сайт, і його телефон починає грітися, як піч. 2. Lagging (гальмування): Інтерфейс стає «в'язким» при скролі або вводі тексту.
Чи можемо ми навчити нашого «художника» бути розумнішим? Чи можемо ми сказати: "Гей, мама і дитина не змінилися — залиш їх у спокої"?
Відповідь — так. І інструмент для цього — React.memo.
2. 🧠 Теорія: Фейс-контроль для компонентів
Давайте розберемося, як це працює, без нудних формул.
Як працює React за замовчуванням?
React сповідує принцип: "Краще перестрахуватися, ніж щось пропустити". Якщо батько (Parent) каже "Я змінився!", React каже всім дітям (Children): "Перемальовуйтесь!".
Що робить React.memo?
React.memo — це компонент вищого порядку (HOC). Але давайте простіше.
Уявіть, що React.memo — це охоронець (фейс-контроль) на вході у ваш компонент.
Коли React хоче перемалювати компонент, цей охоронець зупиняє його і перевіряє вхідні дані (props):
👮♂️ Охоронець (React.memo): "Стій! Твої пропси (props) змінилися з минулого разу?"
🤖 React: "Емм... ні, пропси ті самі:
name="Alice",age={25}."👮♂️ Охоронець: "Тоді проходь повз. Не чіпай цей компонент. Використовуй результат попереднього рендеру."
Це і є мемоізація (запам'ятовування результату).
🔑 Що треба запам'ятати залізобетонно:
React.memoпрацює через поверхневе порівняння (shallow comparison).- Він перевіряє тільки props. Якщо змінився стан (state) всередині самого компонента — він все одно перемалюється.
- Це інструмент оптимізації, а не логіки. Не використовуйте його, щоб запобігти виконанню коду, використовуйте його, щоб пришвидшити інтерфейс.
3. 🧪 Приклади: Від болю до перемоги
Давайте подивимось на код.
Сцена 1: Проблема (Зайві рухи)
У нас є Parent (лічильник) і Child (просто виводить текст).
// ChildComponent.jsx
const ChildComponent = ({ name }) => {
console.log("Rendering Child..."); // Слідкуйте за консоллю!
return <div>Привіт, {name}!</div>;
};
// ParentComponent.jsx
export default function Parent() {
const [count, setCount] = useState(0);
return (
<div>
<h1>Лічильник: {count}</h1>
<button onClick={() => setCount(count + 1)}>+1</button>
{/* Ми передаємо фіксоване ім'я */}
<ChildComponent name="React Student" />
</div>
);
}
Що тут відбувається?
Коли ви натискаєте кнопку +1:
1. count змінюється.
2. Parent ререндериться.
3. ChildComponent теж ререндериться, і ви бачите в консолі "Rendering Child...", хоча ім'я "React Student" не змінилося!
Сцена 2: Рішення з React.memo
Огортаємо наш компонент у магічну функцію.
import React, { memo } from 'react';
// Огортаємо компонент у memo
const ChildComponent = memo(({ name }) => {
console.log("Rendering Child...");
return <div>Привіт, {name}!</div>;
});
// Parent залишається тим самим...
Результат:
Тепер, коли ви тиснете +1 у батька:
1. Parent ререндериться.
2. React доходить до ChildComponent.
3. memo перевіряє: name було "React Student", стало "React Student". Змін немає.
4. Ререндеру дитини НЕМАЄ. Консоль мовчить.
Ми щойно зекономили ресурси браузера! 🎉
Сцена 3: Пастка (Чому це не працює?)
А тепер увага. Це те, де помиляються 90% новачків.
Уявіть, що ми передаємо в Child не рядок, а об'єкт або функцію.
// ParentComponent.jsx
export default function Parent() {
const [count, setCount] = useState(0);
// УВАГА: Цей об'єкт створюється НАНОВО при кожному рендері Parent!
const styleConfig = { color: 'red' };
return (
<div>
<button onClick={() => setCount(count + 1)}>+1</button>
{/* Передаємо об'єкт у мемоізований компонент */}
<ChildComponent name="Student" styleConfig={styleConfig} />
</div>
);
}
Питання до вас: Чи допоможе тут React.memo?
...
...
Відповідь: Ні! Компонент буде ререндеритись.
Чому?
Тому що в JavaScript { color: 'red' } !== { color: 'red' }. Це різні посилання в пам'яті. Кожен рендер батька створює новий об'єкт. memo бачить нове посилання і пропускає рендер. (Але про те, як це лікувати, ми поговоримо в наступному уроці про useMemo).
4. 🛠 Практична частина
Час забруднити руки кодом. Ось ваше завдання:
Завдання 1: "Детектив"
Створіть простий додаток, де є поле вводу (input) для тексту і великий важкий список (просто компонент, який у циклі виводить 1000 div-ів).
* Проблема: Коли ви друкуєте в input, список гальмує (бо він перемальовується на кожну літеру).
* Рішення: Використайте React.memo для списку. Переконайтеся через console.log, що список більше не ререндериться при вводі тексту.
Завдання 2: "Зламай мене"
Візьміть працюючий приклад із Завдання 1. Передайте в список пропсу onClick={() => console.log('click')}.
* Подивіться, що memo перестало працювати.
* Спробуйте пояснити самому собі вголос, чому так сталося (згадайте про функції та посилання).
Завдання 3: "А що, якщо..."
Що буде, якщо огорнути в memo компонент, який приймає children?
const Box = memo(({ children }) => <div>{children}</div>);
- Спробуйте використати його:
<Box><p>Hello</p></Box>. - Чи допоможе тут
memo? (Спойлер: JSX теж створює об'єкти, подумайте про це).
5. 💡 Мислення розробника (Pro Tips)
Ви можете подумати: "Круто! Я тепер обгорну ВЕСЬ свій додаток у React.memo. Продуктивність полетить у космос!"
Стривайте.
Чому не варто "мемоізувати" все підряд?
- Це не безкоштовно. Порівняння пропсів (
prevPropsvsnextProps) займає час процесора. Якщо компонент легкий (наприклад, проста кнопка), витрати на порівняння можуть бути більшими, ніж просто його перемалювати. - Читабельність коду. Зайві обгортки ускладнюють код.
Як думає Senior Developer:
- "Вимірюй, а не вгадуй". Використовуйте React DevTools Profiler. Якщо ви бачите, що компонент ререндериться і це займає багато часу (жовтий колір на графіку) — тоді застосовуйте
memo. - "Мемоізуй листя, а не гілки". Частіше за все
memoпотрібен для компонентів нижнього рівня (великі таблиці, графіки, списки), які знаходяться всередині компонентів, що часто оновлюються.
6. 🧩 Підсумок
Отже, що ми сьогодні дізналися?
- React за замовчуванням ререндерить все дерево вниз від місця зміни.
React.memo— це охоронець, який каже "Стоп", якщо пропси не змінилися.- Він працює через поверхневе порівняння.
- Ми не використовуємо його всюди, лише там, де є реальні проблеми з продуктивністю.
🔮 Що далі?
Ви бачили проблему в "Сцені 3", де об'єкти та функції ламали нашу оптимізацію? Це класична пастка.
У наступному уроці ми познайомимося з двома найкращими друзями React.memo — хуками useCallback та useMemo. Вони допоможуть нам "заморожувати" функції та об'єкти, щоб наш охоронець міг спокійно виконувати свою роботу.
А поки що — це був CS50... тобто, урок з React. Побачимось!