memo() і стан
Що робить React.memo()
React.memo() - це функція вищого порядку (HOC),
яка "обгортає" функціональний компонент
і каже React:
"Якщо пропси не змінилися - не перемальовуй цей компонент заново."
Синтаксис
const MyComponent = React.memo(function MyComponent(props) {
// ...
});або у стрілковій формі:
const MyComponent = React.memo((props) => {
// ...
});Як працює React.memo()
Зазвичай React перемальовує компонент при кожному рендері батька,
навіть якщо його props не змінилися.
React.memo() робить наступне:
- Зберігає попередні пропси компонента.
- При наступному рендері порівнює нові та старі пропси (поверхнево).
- Якщо пропси не змінилися за посиланням -> компонент не ререндериться.
- Якщо хоча б один проп змінився -> компонент перемальовується.
Без React.memo()
function Child({ value }) {
console.log("Child ререндер");
return <p>{value}</p>;
}
function Parent() {
const [count, setCount] = useState(0);
return (
<>
<button onClick={() => setCount(c => c + 1)}>+</button>
<Child value="static text" />
</>
);
}Щоразу при кліку Parent ререндериться,
і Child теж ререндериться,
хоча його props не змінюються.
З React.memo()
const Child = React.memo(function Child({ value }) {
console.log("Child ререндер");
return <p>{value}</p>;
});Тепер Child не ререндериться,
поки його пропси (value) не зміняться.
Продуктивність зростає, особливо при списках, таблицях і великих деревах компонентів.
Як це пов'язано зі станом
- Коли ти викликаєш
setStateу батьківському компоненті -> React ререндерить батька. - За замовчуванням усі дочірні компоненти ререндеряться теж.
React.memo()запобігає цим "зайвим" ререндерам, якщо дочірні компоненти не залежать від зміненого стану батька.
Приклад
function Parent() {
const [count, setCount] = useState(0);
const [text, setText] = useState("");
return (
<div>
<input value={text} onChange={e => setText(e.target.value)} />
<Counter count={count} />
<button onClick={() => setCount(c => c + 1)}>+</button>
</div>
);
}
const Counter = React.memo(({ count }) => {
console.log("Counter ререндер");
return <p>Лічильник: {count}</p>;
});Під час введення тексту Counter не ререндериться,
тому що count (його проп) не змінився.
Без React.memo() ререндер відбувався б на кожен ввід.
Важливо: поверхневе порівняння (shallow compare)
React.memo() порівнює пропси за посиланням, не глибоко.
Приклад:
const user = { name: "Tim" };
<Child data={user} />Якщо при наступному рендері створюється новий об'єкт:
<Child data={{ name: "Tim" }} />React.memo все одно перемалює компонент,
тому що {} !== {} (різні посилання в пам'яті).
Щоб цього уникнути, мемоізуй об'єкти та функції через:
useMemo() / useCallback()Комбінація React.memo + useCallback / useMemo
Дуже часто React.memo використовується разом з мемоізацією колбеків:
const handleClick = useCallback(() => {
console.log("clicked");
}, []); // посилання не змінюється між рендерами
<Child onClick={handleClick} />Без useCallback React буде створювати нову функцію на кожен рендер,
і React.memo не допоможе, тому що пропси будуть "новими" щоразу.
Підсумок
Що робить React.memo() | Обгортає компонент, щоб він ререндерився тільки при зміні пропсів |
|---|---|
| Як працює | Поверхнево порівнює старі та нові пропси |
| Зв'язок зі станом | Запобігає ререндеру дочірніх компонентів, якщо стан батька не впливає на їхні пропси |
| Ключові інструменти поруч | useCallback, useMemo - щоб зберігати стабільні посилання |
| Коли застосовувати | При великих деревах, таблицях, списках, важких дочірніх компонентах |
| Коли не потрібен | У простих компонентах - React сам достатньо оптимізований |
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.