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