DevGang
Авторизоваться

useCallback: Руководство, сценарии использования, примеры

Это легкое для восприятия руководство, в котором мы рассмотрим:

  • Что представляет собой useCallback в React?
  • Когда использовать useCallback? (сценарии использования)
  • Реальные примеры использования UseCallback
  • Советы по устранению неисправностей

Что такое useCallback в React?

useCallback является хуком React. (Хуки - это JS-функции, позволяющие добавлять в компонент дополнительные готовые функции).

UseCallback - это хук, позволяющий кэшировать определение функции между рендерами.

UseCallback не вызывает функцию и не кэширует возвращаемое значение. Он кэширует само определение функции.

Справка о UseCallBack

useCallback(function, [dependencies]);

Поскольку useCallback является хуком react, его необходимо вызывать на верхнем уровне компонента

import { useCallback } from 'react';

export default function ReactCommentsSection({ postId, username }) {
  const handleCommentSubmission = useCallback((Text) => {
    post('/post/' + postId + '/comment', {
      username,
      comment: commentText,
    });
  }, [postId, username]);
}

Параметры

fn: (функция, которую нужно запомнить)

Это функция, которую вы хотите кэшировать.

Эта функция может принимать любые аргументы и возвращать любые данные.

Во время первого рендера React вернет это определение функции (но не вызовет его).

На следующих рендерах:

  • Если dependencies не изменены, то react выдаст вам ту же кэшированную функцию.
  • В противном случае, если зависимости изменились, react выдаст вам текущую функцию, которую вы ему передали. Именно в том случае, если зависимости изменились.

Зависимости

Массив реактивных значений, от которых зависит функция или на которые ссылается fn.

Реактивные значения - это реквизиты, состояние и все переменные/функции, которые находятся внутри вашего компонента.

Reply выполнит неглубокую проверку с помощью метода Object.is, чтобы проверить, изменились ли зависимости.

Для проверки наличия всех зависимостей в массиве зависимостей можно использовать Code linter.

Список зависимостей должен оставаться постоянным и будет записываться в строку в виде массива вида [dep1, dep2, dep3].

Что возвращает useCallback?

Первоначальный рендер:

  • При первом рендере хук useCallback возвращает переданную ему функцию.

Последующие рендеры:

  • Если зависимости не изменились, то будет возвращена кэшированная функция.
  • Если зависимости изменились, то будет возвращена новая функция, то есть та, которую вы передали во время рендеринга.

Примечание

Всегда объявляйте useCallback в верхней части компонента, поскольку это хук.

UseCallback никогда не следует использовать внутри циклов или условий. Если вам необходимо использовать его внутри циклов или условий, извлеките код в новый компонент.

Управление кэшем

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

Примеры и сценарии использования

Вариант использования: Пропуск повторного рендеринга компонентов для оптимизации производительности

При разработке крупномасштабных приложений или приложений со сложной логикой возникает необходимость оптимизации производительности.

Из-за ограниченности ресурсов, логики или масштаба перебора компонентов и реквизитов пользовательский интерфейс может работать не оптимально.

Кэширование значений или функций

Одним из методов оптимизации производительности является кэширование значений, требующих больших вычислений и мало изменяющихся при переходе от одного рендера к другому.

Можно использовать useMemo для кэширования значений, но как быть с функциями?

Как обозначение {} создает новый объект, так и обозначение функции, например, function () {} или () => {}, создает новую функцию.

Обычно это не вызывает проблем, но создание новой функции при каждом повторном рендеринге лишает кэширование смысла.

Таким образом, нам необходимо кэшировать функции, которые мы передаем в качестве реквизитов, и здесь нам пригодится useCallback.

Пример 1: CommentSection

Рассмотрим раздел комментариев на сайте. Здесь у нас есть компонент CommentSection.

import { useCallback } from 'react';

export default function CommentSection({ userName, userToken, postId, }) {
  const handleCommentSubmission = useCallback((textOfTheComment) => {
    post('/post/' + postId + '/comment', {
      username,
      comment: textOfTheComment,
      token: userToken,
    });
  }, [postId, userName, userToken]);
}

Здесь у нас есть компонент CommentSection. Наш компонент принимает три реквизита: userName, userToken и postId.

Затем у нас есть функция handleCommentSection, которая имеет хук useCallback, проверяющий, не изменилась ли какая-либо из зависимостей в массиве зависимостей.

Зависимостями являются: [postId, userName, userToken].

Если зависимости не изменились, то useCallback возвращает кэшированную функцию, в противном случае useCallback возвращает новую функцию.

Когда использовать useCallback и useMemo?

И useCallback, и useMemo используются для кэширования чего-либо с целью оптимизации производительности.

useMemo используется для кэширования значений, а useCallback - для кэширования определений функций.

Таким образом, они используются для того, чтобы предотвратить ненужные повторные рендеринги.

Различие между useMemo и useCallback

Хук useMemo принимает в качестве аргументов функцию и массив зависимостей.

UseMemo вызывает функцию и кэширует ее возвращаемое значение, после чего функция вызывается только при изменении любой из зависимостей.

const ComponentCompute = useMemo(() => {
  return calculateReq(product);
}, [product]);

Здесь возвращаемое значение calculateReq будет кэшироваться и пересчитываться только при изменении product.

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

useMemo

  const requirements = useMemo(() => { // Calls your function and caches its result   
 return computeRequirements(product);  }, [product]);

useCallback

import { useCallback } from 'react';

export default function CommentSection({ userName, userToken, postId, }) {
  const handleCommentSubmission = useCallback((textOfTheComment) => {
    post('/post/' + postId + '/comment', {
      username,
      comment: textOfTheComment,
      token: userToken,
    });
  }, [postId, userName, userToken]);
}

Когда вызывается функция handleCommentSubmission, useMemo вызывает кэшированную версию, которая остается неизменной во всех рендерах, за исключением случаев, когда одна из зависимостей меняется.

Если вы знакомы с useMemo, то можете рассматривать useCallback как обертку вокруг useMemo со встроенной функциональностью.

Можно использовать useMemo вместо useCallback, добавив функцию обратного вызова следующим образом:

function useCallback(fn, dependencies) {
  return useMemo(() => fn, dependencies);
}

Когда следует использовать useCallback?

Это зависит от типа вашего приложения или веб-сайта. Если приложение больше похоже на веб-сайт, то есть вы изменяете сразу большое количество частей пользовательского интерфейса, то запоминание кэширования не так уж полезно. Если же вам нужно изменять только небольшие части общего пользовательского интерфейса, как, например, в приложении для рисования или другом приложении, где вы загружаете сразу много пользовательского интерфейса и затем должны вносить в него небольшие изменения, то кэширование будет хорошей стратегией.

Вот несколько случаев, когда использование useCallback является хорошей идеей:

  1. Если вы используете useMemo в компоненте и хотите передать функцию этому компоненту в качестве свойства.
  2. Если вы используете функцию внутри других хуков React. Например, другая функция, обернутая в useCallback, зависит от нее, а вы зависите от этой функции через useEffect.

Примечание: UseCallback не препятствует созданию функции. Функция всегда создается, и это хорошо, просто React игнорирует созданную функцию и предоставляет вместо нее кэшированную версию.

Сделайте запоминание и кэширование ненужными, следуя нескольким принципам:

1. Визуально оберните компонент

Когда компонент визуально оборачивает другие компоненты, например, большой компонент веб-страницы, содержащий более мелкие компоненты пользовательского интерфейса, такие как кнопки и т.д.

Пусть он принимает JSX в качестве своих дочерних элементов:

<div>
  <img />
    <button />
</div>

Тогда при обновлении компонента-обертки react будет знать, что дочерние компоненты обновлять не нужно.

2. Местное самоуправление

Всегда отдавайте предпочтение местному самоуправлению и не отменяйте его. Прибегайте к помощи только в тех случаях, когда это необходимо.

Таким образом, это важно при работе с кратковременными данными, например, когда пользователь наводит курсор на кнопку или заполняет небольшую форму.

3. Сохранение чистоты логики рендеринга

Компонент всегда должен быть чистым. Это означает, что при заданном входном реквизите выход должен всегда оставаться неизменным или повторный рендеринг компонента никогда не должен приводить к различным результатам.

Исправьте ошибку вместо кэширования компонента.

4. Избегайте ненужных эффектов, обновляющих состояние

Эффекты - это обновления, которые приложение просит выполнить react после завершения обновления пользовательского интерфейса. Однако если обновлений много, то это заставляет приложение работать циклично и вызывает проблемы с производительностью.

Следовательно, используйте обновления только в случае необходимости.

5. Удаление ненужных зависимостей из эффектов

Вместо того чтобы кэшировать значения и объявление функции, можно попытаться уменьшить зависимости, например, убрать некоторые объекты или функции.

Если процесс взаимодействия по-прежнему нестабилен, воспользуйтесь профилировщиком React dev tools, чтобы выяснить, какие компоненты могут получить преимущества от запоминания.

Разница между useCallback и объявлением функции напрямую

Объявление функции напрямую

Прямое объявление функции внутри компонента создает новый экземпляр функции при каждом рендеринге компонента. Это происходит потому, что, как {} создает новый объект, так и функциональное объявление типа function (){} или ()=>{} создает новую функцию.

function SomeComponent(props) {
  function handleClick() {
    console.log('Somebody clicked the button');
  }

  return <button onClick={handleClick}>Click the button     </button>;
}

Здесь каждый раз, когда компонент SomeComponent выводится на экран, создается новая кнопка handleClick.

useCallback

Хук useCallback запоминает само определение функции. Теперь, когда компонент SomeComponent выводится на экран, useCallback выдает кэшированную версию метода handleClick.

import { useCallback } from 'react';

function SomeComponent(props) {
  const handleClick = useCallback(() => {
    console.log('user clicked button');
  }, []);  // function get recreated when dependencies change, here the array is empty meaning the function will never be recreated

  return <button onClick={handleClick}>Click the Button!</button>;
}

При использовании useCallback метод handleClick создается только один раз и кэшируется react, причем кэшированная копия будет использоваться всегда, что повышает производительность.

Основные отличия

  • Прямое создание функции приведет к созданию нового экземпляра функции при каждом повторном рендеринге компонента.
  • useCallback будет кэшировать функцию и повторно использовать определение функции, таким образом, каждый раз выдавая кэшированную копию.

Обновление состояния из запомненного обратного вызова

В React можно обновить состояние компонента с помощью функции setState.

Существует два способа, позволяющих обновить состояние на основе предыдущего состояния:

  1. Чтение текущего состояния изнутри обратного вызова
  2. Использование функции обновления

Чтение текущего состояния изнутри обратного вызова

const handleTodo = useCallback((todoText) => {
  const newTodo = { id: nextId++, todoText };
  setTodos([...todos, newTodo]);
}, [todos]);

Здесь:

  • Мы запоминаем или кэшируем функцию handleTodo с помощью useCallback
  • Переменная состояния todos указана как зависимость, поскольку мы читаем состояние напрямую
  • При каждом изменении todos будет создаваться новая версия handleTodo

Использование функции обновления без прямой зависимости от состояния

const handleAddTodo = useCallback((text) => {
  const newTodo = { id: nextId++, text };
  setTodos(todos => [...todos, newTodo]);
}, []);
  • Здесь вместо того, чтобы читать todos внутри, мы читаем todos напрямую и, следовательно, создаем отсутствие необходимости добавлять его как зависимость в массив зависимостей useCallback.
  • Мы предоставляем функцию обновления. Эта функция обновления принимает в качестве аргумента предыдущий todos и вычисляет новое состояние todos.
  • Таким образом, преимущество заключается в том, что handleTodo больше не нужно обновлять при каждом изменении todos, что позволяет больше использовать кэшированную версию и повысить производительность.

Предотвращение слишком частого применения эффекта

Часто бывает так, что вы используете хук useEffect и вам нужно вызвать функцию внутри хука.

Это создает проблему - каждое значение должно быть объявлено в массиве зависимостей.

Для оптимизации работы useEffect и рендеринга компонента в целях повышения производительности можно обернуть функцию, вызываемую из useEffect, в useCallback.

Оптимизация пользовательского хука

Хуки - это функции, предназначенные для заключения в них многократно используемой функциональности. Поэтому можно создавать и собственные хуки, что способствует многократному использованию и сопровождению кода.

Необходимость оптимизации пользовательских хуков

Существует необходимость оптимизации пользовательских хуков, возвращающих функции.

Это связано с тем, что такие хуки могут стать проблемными для компонентов, которые имеют возвращаемую функцию в качестве зависимости.

Повторное создание этих функций при каждом рендеринге компонента может привести к проблемам с производительностью.

Вы можете использовать хук useCallback для кэширования этих функций, что позволит оптимизировать производительность.

Заключение

В этой статье мы познакомились с хуком useCallback React и рассмотрели примеры его использования.

Надеюсь, что статья вам понравилась. Спасибо, что прочитали.

Источник:

#JavaScript #React
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

В этом месте могла бы быть ваша реклама

Разместить рекламу