Как стать автором
Обновить

Когда же нужно использовать useCallback

Время на прочтение3 мин
Количество просмотров28K

Все мы знаем, что с появлением React 16.8, в нашу жизнь пришли хуки. Действительно, они упрощают процесс написания кода, используя функциональные компоненты в сравнении с классами, но некоторые хуки вызывают много вопросов у разработчиком. В данной статье мы рассмотрим на примерах, когда же нужно использовать useCallback, а когда можно обойтись без него.

Мы знаем из документации, что useCallback возвращает мемоизированную версию коллбэка. Простыми словами возвращает одну и туже ссылку на функцию, до тех пор, пока не изменится одна из зависимостей.

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

Давайте рассмотрим пример:

У нас имеется страница с инпутом, списком отображения элементов и кнопкой, которая добавляет в список введенный элемент. При клике на сам элемент списка, он будет удаляться. Дефолтный список возьмём из 5 элементов, который мы будем редактировать.

const listOfCities = ['Beijing','Tokyo','Kinshasa','Moscow','Jakarta'];

const Page = () => {
  const [name, setName] = useState("");
  const [list, setList] = useState(listOfCities);

  const handleClick = () => {
    setList([...list, name]);
    setName("");
  };

  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setName(event.target.value);
  };

  const handleRemoveClick = (item: string) => {
    const filteredList = list.filter((listItem) => listItem !== item);

    setList(filteredList);
  };

  console.log("Page render");

  return (
    <div>
      <input type="text" value={name} onChange={handleChange} />
      <button onClick={handleClick}>Add</button>
      <CitiesList list={list} onRemoveClick={handleRemoveClick} />
    </div>
  );
};

И компоненты отображения списка

export const CitiesList = ({list, onRemoveClick}) => {
  console.log("List render");

  return list.map((item) => {
    return <City key={item} city={item} onRemoveClick={onRemoveClick} />;
  });
};

export const City = ({city, onRemoveClick}) => {
  const handleCityClick = () => onRemoveClick(city);

  console.log("Element render");

  return <div onClick={handleCityClick}>{city}</div>;
};

Итак, какие же есть сейчас проблемы с перформансом. Давайте запустим и посмотрим что у нас получилось, и сразу откроем консоль разработчика.

Рендер компонентов
Рендер компонентов
Консоль разработчика
Консоль разработчика

Кажется все логично, произошел рендер страницы, компонента списка и его 5 элементов.

Начинаем вводить в инпут символы и видим, что на каждый введенный символ, происходит аналогичный рендер во всех компонентах. Изменения значения инпут (state name), вызывает рендер во всех дочерних компонентах.

У нас есть несколько callback функции, которые теоретически можно обернуть в useCallback. Разберем их по отдельности.

  • Обернув handleClick, handleChange или handleCityClick в useCallback, увы это никак не улучшит перфоманс, ведь useCallback это тоже функция, которая при каждом рендер, будет заново сравнивать зависимости и возвращать новую или старую ссылку на функцию.

  • Обернув handleRemoveClick в useCallback, это уменьшит количество ререндеров при условии что мы обернем СitiesList в React.memo, подробнее о нем можно посмотреть в документации.

const handleRemoveClick = useCallback(
  (item: string) => {
    const filteredList = list.filter((listItem) => listItem !== item);

    setList(filteredList);
  },
  [list]
);
const CitiesList = React.memo(({ list, onRemoveClick }) => {
  console.log("List render");

  return list.map((item) => {
    return <City key={item} item={item} onRemoveClick={onRemoveClick} />;
  });
});

Теперь при вводе символов в инпуте, компонент СitiesList не перерендеривается. На список из 5 элементов улучшение перформанса будет не столь большим, но наглядно показывает как его можно будет улучшить в случае необходимости.

В данном примере можно было вынести инпут с кнопкой и его state в отдельный компонент, тогда необходимость использования useCallback уже была бы не актуальной, поэтому не делайте оптимизаций производительности до тех пор, пока это действительно не потребуется.


Какие из этого можно сделать вывод

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

Надеюсь, эта статья была вам полезной. Всем пока :)

Теги:
Хабы:
Всего голосов 5: ↑4 и ↓1+3
Комментарии11

Публикации

Истории

Работа

Ближайшие события

15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
22 – 24 ноября
Хакатон «AgroCode Hack Genetics'24»
Онлайн
28 ноября
Конференция «TechRec: ITHR CAMPUS»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань