Передача callback-функцій
Зайві перерендери через колбеки виникають тоді, коли дочірній компонент обгорнутий у React.memo, а посилання на функцію-prop змінюється при кожному рендері батька. Лікується стабільною ідентичністю функції та акуратною мемоізацією.
Що робити
- Роби колбеки стабільними (
useCallback)
javascript
const handleSelect = useCallback((id: string) => {
setSelected(id); // можна через функціональний апдейт
}, []); // залежності тільки ті, що реально потрібніСтабільне посилання -> менше шансів "розбудити" дітей
React.memo.
- Мемоізуй дитину (
React.memo)
javascript
const Item = React.memo(function Item({ onSelect, item }) {
return <button onClick={() => onSelect(item.id)}>{item.title}</button>;
});Без
React.memoу дитини стабільність колбека не врятує - вона і так перерендериться разом із батьком.
- Уникай "фабрик" функцій у JSX
javascript
// bad: щоразу нова функція
{items.map(i => <Item key={i.id} onSelect={() => onSelect(i.id)} item={i} />)}
// good: одна стабільна функція + дані йдуть окремим пропом
const onSelect = useCallback((id: string) => { /* ... */ }, []);
{items.map(i => <Item key={i.id} onSelect={onSelect} item={i} />)}- Використовуй функціональні оновлення, щоб не тягнути state у залежності
javascript
// bad: тягне selected у deps -> ламає стабільність
const onToggle = useCallback(() => setSelected(selected + 1), [selected]);
// good: посилання стабільне
const onToggle = useCallback(() => setSelected(s => s + 1), []);- Коли потрібен "останній" стейт, але стабільне посилання - патерн useEvent (через ref)
javascript
function useEvent<T extends (...a: any[]) => any>(fn: T): T {
const ref = useRef(fn);
useLayoutEffect(() => { ref.current = fn; });
return useCallback(((...args) => ref.current(...args)) as T, []);
}
// Використання:
const onChange = useEvent((value: string) => {
// читає найсвіжіший state/props, але посилання на колбек незмінне
});- Не передавай щоразу нові об'єкти з колбеками
javascript
// bad: options новий -> будить дитину
<Chart options={{ onHover }} />
// good: мемоізуй об'єкт-проп
const chartOptions = useMemo(() => ({ onHover }), [onHover]);
<Chart options={chartOptions} />- Іноді простіше змінити API пропсів
- Замість
onSelect={() => onSelect(item.id)}передайid, а сам обробник створюй усерединіItem. - Або передай один
dispatch(зuseReducer) - його посилання стабільне за контрактом.
- Дроби компоненти і піднімай мемоізацію вище
- Винеси "важку" частину в
React.memo-компонент, щоб навіть при зміні колбека перемальовувався тільки маленький вузол.
Чого не варто робити (поширені пастки)
- Ставити
useCallbackвсюди. Він теж коштує трохи CPU. Став там, де:- дитина мемоізована (
React.memo/memo), і - функція реально йде їй у пропси.
- дитина мемоізована (
- Класти в залежності все підряд. Тримай масив deps мінімальним і коректним; для стейту - частіше функціональні апдейти.
- Використовувати індекс масиву як
keyу списках із колбеками - через перестановки втратиш вигоду мемо і стан дітей.
Коротка відповідь
Для співбесідиPremium
Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.