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

В този урок ще разгледаме следващия хук за оптимизация на производителността useCallback.

Хукът useCallback е подобен на API useMemo, разликата е, че първият кешира стойност между моментите на прерисуване на екрана, а вторият - callback функция. Това ни позволява да не презапускаме ресурсоемките функции, когато не е необходимо и може да се използва при предаване на функция в дъщерни компоненти.

Нека разберем по-подробно с пример. Първо създаваме компонент App и създаваме в него състояние num:

App.jsxconst [num, setNum] = useState(0);

Нека имаме бутон, при клик върху който num се увеличава с 1, и параграф, в който ще извеждаме стойността на num:

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

А сега, да предположим, че в App се извежда и някакъв списък с елементи, който ще допълваме при натискане на друг бутон. За съхранение на елементите от този списък ще създадем състояние items:

App.jsxconst [items, setItems] = useState([]);

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

App.jsxfunction addItem() {
  setItems([...items, 'new item']);
}

Сега нека напишем код за показване на елементите от списъка и да го изнесем в дъщерен компонент Items, който като пропсове ще получава масив от елементи и функция за тяхното добавяне. Да не забравим да добавим извеждане в конзолата, за да виждаме кога нашият Items ще се прерисува:

Items.jsxfunction 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:

App.jsxreturn (
  <>
    <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:

Items.jsxexport default memo(Items);

Да не забравим да импортираме memo:

Items.jsximport { memo } from 'react';

А сега отваряме конзолата и натискаме бутоните. Всички усилия напразно! Ние мемоизирахме компонента, но при натискане на бутона 'click' компонентът Items все пак се прерисува всеки път.

Работата е там, че когато родителският компонент се прерисува, неговите функции се пресъздават наново - това се отнася и за нашата функция addItem, която предаваме в Items.

Точно в този момент ще ни помогне хукът useCallback. Нека го приложим. Първо го импортираме в App:

App.jsximport { useCallback } from 'react';

След това преработваме простото деклариране на функцията addItem в Function Expression, посочваме като първи параметър за useCallback нашата функция под формата на callback. Вторият параметър в квадратни скоби посочваме зависимостите - всички реактивни променливи, участващи във функцията, в нашия случай това е масивът items:

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

Готово! По този начин ние кеширахме функцията. Натискаме отново бутоните и виждаме, че сега при натискане на бутона 'click' нашият дъщерен компонент не се прерисува.

Задание 1

Създайте компонент App, поставете в него параграф с текст. Създайте състояние с начална стойност 'text' и я изведете в параграфа. Нека при клик върху параграфа в края на текста му се добавя удивителен знак.

Задание 2

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

Задание 3

В Products изведете в конзолата текст 'products render'. Обвийте Products в memo. Кликнете върху параграфа и бутона. Уверете се, че при клик върху параграфа дъщерният компонент все пак се прерисува.

Задание 4

Кеширайте функцията за добавяне на продукти, като я обвиете в хука useCallback. Кликнете върху параграфа и бутона. Уверете се, че при клик върху параграфа, дъщерният компонент вече не се прерисува.