# Хук оптимизации производительности useCallback в React

В этом уроке мы рассмотрим следующий хук для оптимизации производительности `useCallback`.

Хук `useCallback` подобен API `useMemo`, отличие заключается в том, что первый кэширует значение между моментами перерисовки экрана, а второй callback-функцию. Это позволяет нам не перезапускать ресурсозатратные функции, когда это не требуется и может использоваться при передаче функции в дочерние компоненты.

Давайте разберемся подробнее на примере. Для начала создадим компонент `App` и заведем в нем стейт `num`:

`const [num, setNum] = useState(0);`

Пусть у нас будет кнопка, по клику на которую `num` увеличивается на `1` и абзац, в котором мы будем выводить значение `num`:

`return ( <div> <button onClick={() => setNum(num + 1)}>click</button> <p>clicks: {num}</p> </div> );`

А сейчас, предположим, что у нас в `App` выводится еще какой-то список с элементами, который мы будем дополнять по нажатию другой кнопки. Для хранения элементов этого списка мы заведем стейт `items`:

`const [items, setItems] = useState([]);`

И затем напишем функцию `addItem` для их добавления:

`function addItem() { setItems([...items, 'new item']); }`

Теперь давайте напишем код для отображения элементов списка и вынесем его в дочерний компонент `Items`, который в виде пропсов будет получать массив элементов и функцию для их добавления. Не забудем добавить вывод в консоль, чтобы видеть когда наш `Items` будет перерисовываться:

`function Items({ items, addItem }) { const result = items.map((item, index) => { return <p key={index}>{item}</p>; }); console.log('Items render'); return ( <div> <h3>Our items</h3> {result} <button onClick={addItem}>add item</button> </div> ); } export default Items;`

Разместим `Items` в конце компонента `App` и будем передавать ему массив `items` и функцию для добавления элементов `addItem`:

`return ( <> <div> <button onClick={() => setNum(num + 1)}>click</button> <p>clicks: {num}</p> <br /> </div> <Items items={items} addItem={addItem} /> </> );`

А теперь понажимаем на кнопочки и убедимся, что `num` растет и новые элементы добавляются в список. А открыв консоль, мы увидим, что наш список перерисовывается каждый раз, даже если мы кликаем по кнопке, которая увеличивает `num`.

Если у нас маленький списочек, то все в порядке, а если предполагается, что он будет объемным и там много чего еще есть? Не беда - скажете вы, ведь на прошлом уроке мы рассмотрели API `memo`, чтобы как раз избегать ненужных перерисовок компонента.

Так давайте обернем наш компонент `Items` в `memo` и все дела. Кстати это можно сделать прямо при экспорте `Items`:

`export default memo(Items);`

Не забудем импортировать `memo`:

`import { memo } from 'react';`

А теперь откроем консоль и понажимаем на кнопочки. Все старания впустую! Мы мемоизировали компонент, но при нажатии на кнопку `'click'` компонент `Items` все равно перерисовывается каждый раз.

Дело все в том, что когда родительский компонент перерисовывается, его функции пересоздаются заново - это касается и нашей функции `addItem`, которую мы передаем в `Items`.

Именно в этот момент нам поможет хук `useCallback`. Давайте применим его. Для начала импортируем его в `App`:

`import { useCallback } from 'react';`

Затем переделаем простое объявление функции `addItem` в Function Expression, укажем в качестве первого параметра для `useCallback` нашу функцию в виде колбэка. Вторым параметром в квадратных скобочках укажем зависимости - все реактивные переменные, участвующие в функции, в нашем случае это массив `items`:

`const addItem = useCallback(() => { setItems(() => [...items, 'New item']); }, [items]);`

Готово! Таким образом мы закэшировали функцию. Нажимаем снова на кнопочки и видим, что теперь при нажатии на кнопку `'click'` наш дочерний компонент не перерисовывается.

Создайте компонент `App`, поместите в него абзац с текстом. Заведите стейт с начальным значением `'text'` и выведите его в абзаце. Пусть по клику на абзац ему в конец текста добавляется восклицательный знак.

Создайте дочерний компонент `Products`, в котором у вас будет кнопка для добавления нового продукта. Разместите его в `App`. В родительском компоненте создайте стейт с массивом продуктов и функцию добавления нового продукта. Передайте их в качестве пропсов в дочерний, выведите в нем переданный массив в виде списка `ul`.

В `Products` выведите в консоль текст `'products render'`. Оберните `Products` в `memo`. Покликайте на абзац и кнопку. Убедитесь, что при клике на абзац дочерний компонент все равно перерисовывается.

Закэшируйте функцию для добавления продуктов, обернув ее в хук `useCallback`. Покликайте на абзац и кнопку. Убедитесь, что при клике на абзац, дочерний компонент больше не перерисовывается.

← →
↑