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