СЕРГІЙ БАБІЧ

awesome frontend developer

Оптимізація

і зайці

Для чого нам потрібні хуки useMemo та useCallback?

думка авдиторії

Що таке ‘re-render’ компонента та коли він відбувається?

думка авдиторії

memo

думка авдиторії

Як насправді працюють “магічні” хуки

useMemo

function updateMemo<T>(
  nextCreate: () => T,
  deps: Array<mixed> | void | null
): T {
  const hook = updateWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  const prevState = hook.memoizedState;
  if (prevState !== null) {
    if (nextDeps !== null) {
      const prevDeps: Array<mixed> | null = prevState[1];
      if (areHookInputsEqual(nextDeps, prevDeps)) {
        return prevState[0];
      }
    }
  }
  if (shouldDoubleInvokeUserFnsInHooksDEV) {
    nextCreate();
  }
  const nextValue = nextCreate();
  hook.memoizedState = [nextValue, nextDeps];
  return nextValue;
}

useCallback

function updateCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
  const hook = updateWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  const prevState = hook.memoizedState;
  if (prevState !== null) {
    if (nextDeps !== null) {
      const prevDeps: Array<mixed> | null = prevState[1];
      if (areHookInputsEqual(nextDeps, prevDeps)) {
        return prevState[0];
      }
    }
  }
  hook.memoizedState = [callback, nextDeps];
  return callback;
}

areHookInputsEqual

function areHookInputsEqual(
  nextDeps: Array<mixed>,
  prevDeps: Array<mixed> | null,
) {
  ...
  for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
    if (is(nextDeps[i], prevDeps[i])) {
      continue;
    }
    return false;
  }
  return true;
}

Передчасний оптимізаєць

const a = useValue();
const b = useAnotherValue();

const c = useMemo(() => a + b, [a, b]);

return <MyComponent value={c} />;
  1. Створили функцію і масив залежностей;
  2. Викликали `useMemo`;
  3. Прочитали поточний fiber state;
  4. Перевірили залежності на рівність;
  5. Вернули збережене значення, АБО;
  6. викликали `create`-функцію, зберегли значення, вернули;
  7. передали значення в компонент
const a = useValue();
const b = useAnotherValue();

const c = a + b;

return <MyComponent value={c} />;
  1. Додали два числа
  2. передали значення в компонент
const array = [...]
const onClick = useCallback((item, index) => { ... }, []);
const showModal = useCallback(() => { ... }, []);

return (
  <>
    <Button onClick={showModal}/>
    {array.map((item, index) =>(
      <MyComponent  onClick={() => onClick(item, index)} />
    ))}
  </>
)
  1. Створили функцію і масив залежностей;
  2. Викликали `useCallback`;
  3. Прочитали поточний fiber state;
  4. Перевірили залежності на рівність;
  5. Вернули збережений інстанс функції, АБО;
  6. зберегли новий інстанс та вернули його;
  7. передали в компонент
const array = [...]
function onItemClick(item, index) { ... };
function showModal() { ... };

return (
  <>
    <Button onClick={showModal}/>
    {array.map((item, index) =>(
      <MyComponent onClick={() => onItemClick(item, index)} />
    ))}
  </>
)
  1. Створили функцію
  2. передали в компонент

Ре-рендер відбудеться в будь-якому випадку, якщо вам компонент не мемоїзований явно через memo.

А як же garbage collector?!
А ніяк. Ви робіть свою роботу, він робить свою.

Два слова про час

  • Людина тупо не помічає всього, шо менше за 0,1 секунди;
  • Якщо ви покращите обробку масива з трьох елементів з 10 мс до 1мс, про це знатимете лише ви. Користувачам то до спини (див. п.1);
  • Час, затрачений на таку "оптимізацію", можна було б із більшою користю провести, длубаючи у носі;

Много слів про оптимізацію

  • useMemo і useCallback не оптимізують
  • Вони мають забезпечити сталий референс
  • Вони не гарантують кешування
  • Дуже часто виконання useMemo в рази дорожче, ніж сам обрахунок значення
  • Примітиви і так порівнюються за значенням, а не за посиланням
  • Якщо у вас в useMemo виконуються важкі обчислення та перетворення, спробуйте винести їх за компонент
  • useCallback часто безглуздий при використанні в масивах
  • На мою особисту думку більшість проблем з оптимізацією виникають через невдалий дизайн даних та ієрархії компонентів
  • Зміна структури даних та прибрання зайвих ре-рендерів в дочірніх компонентах допоможе з оптимізацією в десятки разів краще

Висновки

  • useMemo та useCallback вас не врятують
  • оптимізуйте, лише коли маєте проблеми
  • оптимізуйте дані та вартість ре-рендера замість пропсів

Дякую. Тепер питання. Бігом.

ютуб канал «Сергій Бабіч та Дивовижний світ веб розробки»