Skip to main content

Передача callback-функцій

Зайві перерендери через колбеки виникають тоді, коли дочірній компонент обгорнутий у React.memo, а посилання на функцію-prop змінюється при кожному рендері батька. Лікується стабільною ідентичністю функції та акуратною мемоізацією.


Що робити

  1. Роби колбеки стабільними (useCallback)
javascript
const handleSelect = useCallback((id: string) => { setSelected(id); // можна через функціональний апдейт }, []); // залежності тільки ті, що реально потрібні

Стабільне посилання -> менше шансів "розбудити" дітей React.memo.

  1. Мемоізуй дитину (React.memo)
javascript
const Item = React.memo(function Item({ onSelect, item }) { return <button onClick={() => onSelect(item.id)}>{item.title}</button>; });

Без React.memo у дитини стабільність колбека не врятує - вона і так перерендериться разом із батьком.

  1. Уникай "фабрик" функцій у 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} />)}
  1. Використовуй функціональні оновлення, щоб не тягнути state у залежності
javascript
// bad: тягне selected у deps -> ламає стабільність const onToggle = useCallback(() => setSelected(selected + 1), [selected]); // good: посилання стабільне const onToggle = useCallback(() => setSelected(s => s + 1), []);
  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, але посилання на колбек незмінне });
  1. Не передавай щоразу нові об'єкти з колбеками
javascript
// bad: options новий -> будить дитину <Chart options={{ onHover }} /> // good: мемоізуй об'єкт-проп const chartOptions = useMemo(() => ({ onHover }), [onHover]); <Chart options={chartOptions} />
  1. Іноді простіше змінити API пропсів
  • Замість onSelect={() => onSelect(item.id)} передай id, а сам обробник створюй усередині Item.
  • Або передай один dispatchuseReducer) - його посилання стабільне за контрактом.
  1. Дроби компоненти і піднімай мемоізацію вище
  • Винеси "важку" частину в React.memo-компонент, щоб навіть при зміні колбека перемальовувався тільки маленький вузол.

Чого не варто робити (поширені пастки)

  • Ставити useCallback всюди. Він теж коштує трохи CPU. Став там, де:
    1. дитина мемоізована (React.memo/memo), і
    2. функція реально йде їй у пропси.
  • Класти в залежності все підряд. Тримай масив deps мінімальним і коректним; для стейту - частіше функціональні апдейти.
  • Використовувати індекс масиву як key у списках із колбеками - через перестановки втратиш вигоду мемо і стан дітей.

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

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

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