Главная Категории Контакты Поиск

Оптимизация через useCallback

Как остановить запуск useEffect при каждом рендеринге с помощью useCallback

React·18.02.2021·читать 2 мин 🤓·Автор: Alexey Myzgin

Если ты добавляешь функцию в массив зависимостей useEffect и получаешь бесконечное количество повторных отрисовок - пришло время использовать useCallback!

Синтаксис:

const memoizedCallback = useCallback(() => {
  doSomething(a, b);
}, [a, b]);

useCallback возвращает новую версию функции только при изменении ее зависимостей. В приведенном выше примере это работает только при изменении a или b.

Это означает, что даже при повторном рендеринге компонента ты можешь быть уверен, что функция, заключенная в useCallback, не будет повторно объявлена, что предотвратит ужасный бесконечный цикл повторного рендеринга / useEffect.

Прежде чем мы пойдем в глубь useCallback, давай быстро освежим в памяти компоненты React.

Повторение

Вот компонент React, который отображает дочерний компонент. Иногда может произойти что-то, что приведет к повторному рендерингу компонента (например, изменение свойств props или вызов useState).

function SomeComponent(props) {
  return <DataFetcher />;
}

Теперь, если мы хотим передать <DataFetcher /> props, например функцию, которая генерирует URL-адрес для выборки данных, можем определить функцию следующим образом:

function SomeComponent(props){
  function getUrl(id){
    return "https://some-api-url.com/api/" + id + "/"
  }
  return <DataFetcher getUrl={getUrl}>
}

Прежде чем беспокоиться о хуках, мы могли бы определить функцию в классе и передать ее дочерним элементам, которые могли бы без проблем запускать this.getUrl(id). Однако теперь, когда мы находимся в функциональном компоненте, то как мы определили getUrl, означает нечто иное.

Поскольку по умолчанию весь код SomeComponent повторно запускается при рендеринге, getUrl переопределяется при каждом рендеринге.

Если DataFetcher затем использует getUrl как часть хука useEffect, даже если ты добавляешь getUrl в массив зависимостей, твой useEffect будет запускаться каждый рендер.

useEffect(() => {
  fetchDataToDoSomething(getUrl);
}, [getUrl]); // 🔴 повторно запускает useEffect каждый рендер

Как остановить запуск useEffect каждый рендер?

Вернувшись в SomeComponent, у нас есть два варианта решения этой проблемы (при условии, что мы не можем просто переместить getUrl в проблемный хук useEffect).

Старомодный способ: переместить getUrl за пределы компонента, чтобы он не объявлялся повторно при каждом рендере:

function getUrl(id){
   return "https://some-api-url.com/api/" + id + "/"
}
function SomeComponent(props){
   return <DataFetcher getUrl={getUrl}>
}

Способ Hooks, заключающийся в том, чтобы обернуть getUrl в useCallback:

function SomeComponent(props){
   const getUrl = useCallback(function (id) {
     return "https://some-api-url.com/api/" + id + "/";
   }, []); // <-- Обрати внимание, что в этом случае ты не можешь добавить id в массив deps.
   return <DataFetcher getUrl={getUrl}>
}

Конечно, это довольно упрощенный пример, показывающий, что ты можешь сделать, чтобы исправить гораздо более сложный код.

Я не предлагаю тебе использования useCallback / useMemo везде или избегать их.

Ключевой вывод заключается в том, что useCallback возвращает новую версию функции только при изменении её зависимостей, избавляя дочерние компоненты от автоматического повторного рендеринга каждый раз, при рендеринге родительского компонента. Это особенно полезно при использовании с useEffect, поскольку можно безопасно добавлять функции, заключенные в useCallback, в массив зависимостей, не опасаясь бесконечных повторных отрисовок.

Website, name & logo
Copyright © 2022. Alex Myzgin