Изучение того, как приручить React useCallback Hook
Опубликовано: 2022-04-27Ни для кого не секрет, что React.js стал широко популярен в последние годы. Теперь это библиотека JavaScript, которую предпочитают многие из самых известных игроков в Интернете, включая Facebook и WhatsApp.
Одной из основных причин его появления стало введение хуков в версии 16.8. Хуки React позволяют вам использовать функциональность React без написания компонентов класса. Теперь функциональные компоненты с хуками стали основной структурой разработчиков для работы с React.
В этом посте мы углубимся в один конкретный хук — useCallback
— потому что он затрагивает фундаментальную часть функционального программирования, известную как мемоизация. Вы будете точно знать, как и когда использовать хук useCallback
, и максимально использовать его возможности повышения производительности.
Готовый? Давайте погрузимся!
Что такое мемоизация?
Мемоизация — это когда сложная функция сохраняет свои выходные данные, чтобы в следующий раз она вызывалась с теми же входными данными. Это похоже на кэширование, но на более локальном уровне. Он может пропускать любые сложные вычисления и быстрее возвращать результат, поскольку он уже рассчитан.
Это может оказать существенное влияние на распределение памяти и производительность, и именно эту нагрузку призван облегчить хук useCallback
.
Использование React useCallback против useMemo
На данный момент стоит упомянуть, что useCallback
прекрасно сочетается с другим хуком под названием useMemo
. Мы обсудим их обоих, но в этой статье мы сосредоточимся на useCallback
как на основной теме.
Ключевое отличие состоит в том, что useMemo
возвращает запомненное значение, тогда как useCallback
возвращает запомненную функцию. Это означает, что useMemo
используется для хранения вычисляемого значения, а useCallback
возвращает функцию, которую вы можете вызвать позже.
Эти хуки вернут вам кешированную версию, если не изменится одна из их зависимостей (например, состояние или свойства).
Давайте посмотрим на две функции в действии:
import { useMemo, useCallback } from 'react' const values = [3, 9, 6, 4, 2, 1] // This will always return the same value, a sorted array. Once the values array changes then this will recompute. const memoizedValue = useMemo(() => values.sort(), [values]) // This will give me back a function that can be called later on. It will always return the same result unless the values array is modified. const memoizedFunction = useCallback(() => values.sort(), [values])
Фрагмент кода выше является надуманным примером, но показывает разницу между двумя обратными вызовами:
-
memoizedValue
станет массивом[1, 2, 3, 4, 6, 9]
. Пока сохраняется переменная values, сохраняется иmemoizedValue
, и она никогда не будет пересчитываться. -
memoizedFunction
будет функцией, которая вернет массив[1, 2, 3, 4, 6, 9]
.
Что хорошего в этих двух обратных вызовах, так это то, что они кэшируются и зависают до тех пор, пока не изменится массив зависимостей. Это означает, что при рендеринге они не будут собирать мусор.
твитнутьРендеринг и реакция
Почему мемоизация важна, когда речь идет о React?
Это связано с тем, как React отображает ваши компоненты. React использует виртуальный DOM, хранящийся в памяти, для сравнения данных и принятия решения о том, что нужно обновить.
Виртуальный DOM помогает React повысить производительность и поддерживает скорость вашего приложения. По умолчанию, если какое-либо значение в вашем компоненте изменится, весь компонент будет перерисован. Это делает React «реагирующим» на пользовательский ввод и позволяет обновлять экран без перезагрузки страницы.
Вы не хотите отображать свой компонент, потому что изменения не повлияют на этот компонент. Здесь пригодится мемоизация через useCallback
и useMemo
.
Когда React перерисовывает ваш компонент, он также воссоздает функции, которые вы объявили внутри вашего компонента.
Обратите внимание, что при сравнении равенства функции с другой функцией они всегда будут ложными. Поскольку функция также является объектом, она будет равна только самой себе:
// these variables contain the exact same function but they are not equal const hello = () => console.log('Hello Matt') const hello2 = () => console.log('Hello Matt') hello === hello2 // false hello === hello // true
Другими словами, когда React повторно визуализирует ваш компонент, он увидит все функции, объявленные в вашем компоненте, как новые функции.
В большинстве случаев это нормально, а простые функции легко вычисляются и не влияют на производительность. Но в других случаях, когда вы не хотите, чтобы функция воспринималась как новая функция, вы можете положиться на useCallback
, чтобы помочь вам.
Вы можете подумать: «Когда я не хочу, чтобы функция рассматривалась как новая функция?» Ну, есть определенные случаи, когда useCallback
имеет больше смысла:
- Вы передаете функцию другому компоненту, который также запоминается (
useMemo
) - Ваша функция имеет внутреннее состояние, которое она должна помнить
- Ваша функция зависит от другого хука, например,
useEffect
Преимущества производительности React useCallback
Когда useCallback
используется надлежащим образом, это может помочь ускорить ваше приложение и предотвратить повторную визуализацию компонентов, если в этом нет необходимости.
Скажем, например, у вас есть компонент, который извлекает большой объем данных и отвечает за отображение этих данных в виде диаграммы или графика, например:
Предположим, что родительский компонент для компонента вашей визуализации данных повторно визуализируется, но измененные реквизиты или состояние не влияют на этот компонент. В этом случае вы, вероятно, не хотите или не нуждаетесь в повторном рендеринге и повторной выборке всех данных. Отказ от повторного рендеринга и повторной загрузки может сэкономить пропускную способность вашего пользователя и обеспечить более плавный пользовательский интерфейс.
Недостатки React useCallback
Хотя этот хук может помочь вам улучшить производительность, он также имеет свои подводные камни. Перед использованием useCallback
(и useMemo
) необходимо учитывать следующие моменты:
- Сборка мусора: другие функции, которые еще не запомнены, React выбрасывает, чтобы освободить память.
- Распределение памяти: аналогично сборке мусора, чем больше у вас запоминаемых функций, тем больше памяти потребуется. Кроме того, каждый раз, когда вы используете эти обратные вызовы, внутри React появляется куча кода, которому нужно использовать еще больше памяти, чтобы предоставить вам кешированный вывод.
- Сложность кода: когда вы начинаете оборачивать функции в эти хуки, вы сразу же увеличиваете сложность своего кода. Теперь требуется более глубокое понимание того, почему используются эти хуки, и подтверждение того, что они используются правильно.
Знание вышеперечисленных подводных камней может избавить вас от головной боли, связанной с тем, чтобы споткнуться о них самостоятельно. При рассмотрении вопроса об использовании useCallback
убедитесь, что преимущества производительности перевешивают недостатки.
Реагировать на пример useCallback
Ниже показана простая установка с компонентом «Кнопка» и компонентом «Счетчик». Счетчик имеет две части состояния и отображает два компонента Button, каждый из которых будет обновлять отдельную часть состояния компонентов счетчика.
Компонент Button принимает два реквизита: handleClick
и name. Каждый раз, когда кнопка визуализируется, она регистрируется в консоли.
import { useCallback, useState } from 'react' const Button = ({handleClick, name}) => { console.log(`${name} rendered`) return <button onClick={handleClick}>{name}</button> } const Counter = () => { console.log('counter rendered') const [countOne, setCountOne] = useState(0) const [countTwo, setCountTwo] = useState(0) return ( <> {countOne} {countTwo} <Button handleClick={() => setCountOne(countOne + 1)} name="button1" /> <Button handleClick={() => setCountTwo(countTwo + 1)} name="button1" /> </> ) }
В этом примере всякий раз, когда вы нажимаете любую кнопку, вы увидите это в консоли:
// counter rendered // button1 rendered // button2 rendered
Теперь, если мы применим useCallback
к нашим функциям handleClick
и обернем нашу кнопку в React.memo
, мы увидим, что нам дает useCallback
. React.memo
похож на useMemo
и позволяет нам запоминать компонент.
import { useCallback, useState } from 'react' const Button = React.memo(({handleClick, name}) => { console.log(`${name} rendered`) return <button onClick={handleClick}>{name}</button> }) const Counter = () => { console.log('counter rendered') const [countOne, setCountOne] = useState(0) const [countTwo, setCountTwo] = useState(0) const memoizedSetCountOne = useCallback(() => setCountOne(countOne + 1), [countOne) const memoizedSetCountTwo = useCallback(() => setCountTwo(countTwo + 1), [countTwo]) return ( <> {countOne} {countTwo} <Button handleClick={memoizedSetCountOne} name="button1" /> <Button handleClick={memoizedSetCountTwo} name="button1" /> </> ) }
Теперь, когда мы нажмем любую из кнопок, мы увидим только ту кнопку, которую мы нажали для входа в консоль:
// counter rendered // button1 rendered // counter rendered // button2 rendered
Мы применили мемоизацию к нашему компоненту кнопки, и передаваемые ему значения реквизита воспринимаются как равные. Две функции handleClick
кэшируются и будут рассматриваться React как одна и та же функция до тех пор, пока значение элемента в массиве зависимостей не изменится (например, countOne
, countTwo
).
Резюме
Какими бы крутыми ни были useCallback
и useMemo
, помните, что у них есть конкретные варианты использования — вы не должны оборачивать каждую функцию этими хуками. Если функция сложна в вычислительном отношении, зависимость от другого хука или свойства, переданного мемоизированному компоненту, являются хорошими индикаторами, которые вы, возможно, захотите использовать для useCallback
.
Мы надеемся, что эта статья помогла вам понять эту расширенную функциональность React и помогла вам обрести больше уверенности в функциональном программировании на этом пути!