Хук оптимизации производительности 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. Покликайте на абзац и кнопку. Убедитесь, что при клике на абзац, дочерний компонент больше не перерисовывается.



Чат с GPT Компилятор