Шпаргалка по хукам React: базовый уровень
1. Зачем в React нужны хуки?
Хуки подходят для решения следующих задач.
1. Повторное использование логики с отслеживанием состояния (stateful logic) без изменения иерархии компонентов. Хуки помогают извлечь такую логику из компонента, независимо протестировать и повторно использовать ее. Такие хуки можно использовать в разных компонентах, ими легко обмениваться с другими членами команды.
Требующие реструктуризации компонентов паттерны — рендер-пропы и компоненты высшего порядка (HOC — Higher Order Components) — усложняют работу, затрудняя отслеживание кода. Хуки упрощают и эти задачи.
2. Разделение компонента на более мелкие функции. Хуки упрощают программирование методов жизненного цикла. Каждый из таких методов часто содержит набор несвязанной логики. Компоненты могут выполнять выборку данных с помощью componentDidMount()
. В то же время этот метод может включать и некоторую несвязанную логику, настраивающую слушателей событий.

То же самое можно сделать со встроенными хуками React, такими как useState
и useEffect
:

Код становится проще и короче, что особенно ценно для команды разработчиков. Индивидуально обрабатываемые методы жизненного цикла, такие как componentDidMount()
, componentDidUpdate()
и componentWillUnmount()
, может заменить один хук React useEffect()
.
3. Больше функций React без классов. До появления хуков в React использовались в основном компоненты на основе классов. Это требовало больших (а иногда и ненужных) усилий, потому что часто приходилось переключаться между классами, отображать пропы, HOC и т. д. Теперь благодаря хукам все это можно делать без переключений, используя функциональные компоненты.
- Использование компонентов на основе классов:

- Использование функциональных компонентов:

Если сравнивать оба эти варианта, второй подход с функциональным компонентом имеет значительно более простой код, а результаты одинаковые. Здесь не нужно выделять место для экземпляра класса, а затем вызывать функцию render()
. Вместо этого просто вызывается функция.
2. Хук useState
Хук
useState
позволяет использовать в функциональном компоненте состояние локального приложения.
Он возвращает хранящее состояние значение (stateful) вместе с функцией своего обновления. Вот основная сигнатура вызова:

Используемая для обновления состояния функция setState
принимает новое значение состояния (newState
):

Имеется несколько важных особенностей при использовании useState
.
1. Объявление переменной состояния. Чтобы объявить переменную состояния, нужно просто вызвать useState
с некоторым начальным значением:

2. Обновление переменной состояния. В этом случае нужно вызвать функцию обновления, возвращаемую при вызове useState
:

3. Использование переменных с разными состояниями. Их можно применять, обновляя внутри функционального компонента. Например:

4. Использование переменных состояния объекта. Весь объект можно использовать в качестве начального значения, передаваемого useState
. При этом не будет автоматического слияния обновляемых объектов.

Новое состояние объекта: { name: "John", age: "unknown" }
.
5. Функциональность useState.
Возвращаемая после вызова useState
функция обновления может быть подобна использованной в setState
основанного на классе компонента:

Обе эти формы применимы для вызова updateValue
.
3. Хук useEffect
Хук
useEffect
принимает функцию для выполнения любых побочных эффектов.
Переданная в useEffect
функция обычно будет запущена после завершения отработки на экране рендера. Но активировать эти эффекты можно и в зависимости от изменения определенных значений.
Сигнатура вызова useEffect
:

Для создания базового побочного эффекта используем в следующем примере хуки useState
и useEffect
:

Блок useEffect
обновляет заголовок текущей вкладки/окна браузера после выполнения функции handleClick
.
Полезные особенности useEffect
:
1. Очистка эффекта. Периодически нужно очищать effect, обычно это делают возвратом функции из переданной в useEffect
функции эффекта (effectFunction).

Эта функция очистки выполняется для освобождения памяти перед удалением компонента из пользовательского интерфейса. К тому же создание нового кода очистки означает, что предыдущий эффект очищается перед выполнением следующего.
2. Создание нескольких эффектов. Добавить несколько эффектов позволяют несколько вызовов useEffect
в одном функциональном компоненте. Пример:

3. Пропуск эффектов. Чтобы не вызывать хук при каждом рендере, можно пропускать вызовы useEffect
. Сделать это можно разными способами:
- Использование массива зависимостей (dependency array). При этом в useEffect передается массив значений. Таким образом функция effect будет вызываться в процессе монтажа “and” при любой передаче сгенерированного нами значения. Например:

- Использование пустого массива зависимостей. В этом случае useEffect передает пустой массив []. Теперь функция эффекта вызывается только при монтаже:

- Отсутствие массива зависимостей. Можно полностью пропустить эффекты, вообще не предоставляя массив зависимостей. Функция эффекта будет выполняться после каждого рендера:

4. Правила использования хуков
Для эффективного использования хуков React следует соблюдать два основных правила.
- Вызывайте хуки на верхнем уровне. Другими словами: не вызывайте их внутри циклов, условий или вложенных функций.
Гарантированный таким образом вызов хуков каждый раз будет происходить в одном и том же порядке при рендере компонента. Это поможет сохранить состояние приложения между несколькими вызовами useState
и useEffect
.
Поэтому перед любыми ранними возвратами всегда следует контролировать использование хуков на верхнем уровне функции. React учитывает порядок вызова хуков, поэтому их вызов каждый раз в одном и том же порядке будет выглядеть следующим образом:

Но если поместить вызов хука persistForm
внутрь условия:

Тогда, используя хук внутри условия, нарушается первое правило! Теперь порядок изменится следующим образом:

React не знает, что возвращать при втором вызове useState
. Поэтому несколько пропущенных хуков вызвали проблемы.
2. Вызывайте хуки только из функций React. Другими словами: не вызывайте хуки из обычных функций JavaScript.
Следуя этому правилу, мы гарантируем хорошую наглядность кода логики с состоянием компонента.
Вместо того вызова хуков из обычных функций, можно:
- вызывать хуки из компонентов функций React;
- вызывать хуки из пользовательских хуков.
Шпаргалка по хукам React: средний уровень
1. Создание пользовательских хуков
При необходимости и в дополнение к имеющимся useState
и useEffect
можно создавать и собственные хуки для извлечения логики компонента в повторно используемых функциях.
Пользовательские хуки — это тип функций с префиксом “use”, которые могут вызывать другие функции.
При этом не обязательно иметь конкретную сигнатуру. Вот что следует учитывать при создании пользовательского хука.
- Извлечение пользовательского хука. Пользовательские хуки используются для совместного использования логики между двумя функциями JavaScript с последующим ее извлечением в третью функцию. При этом следует убедиться в безусловном вызове других хуков только на верхнем уровне пользовательского хука. Например:

Здесь useFriendStatus
использует хуки useState
и useEffect
для взаимодействия с фрагментом кода для подписки на статус друга.
2. Использование пользовательских хуков. После создания их можно использовать в любой части компонентов. Например, есть пользовательский хук под названием useBoolean
, возвращающий состояние и функции для обновления состояния массива:

Следует отметить, что initialState
передается в качестве аргумента вместе с его значением по умолчанию (false
). Вернувшись в компонент App
, этот хук можно использовать, передавая ему начальное состояние и используя возвращаемые значения для отображения и обновления состояния. Например:

2. Хук useContext
Устранить зависимость разработчика от потребителя контекста позволяет хук useContext
.
Он принимает паттерн контекстного объекта (context object) и возвращает текущее значение этого контекста.
Контекстный объект берется из React.createContext
и определяется пропом value
, ближайшего (выше вызывающего компонента в дереве) <MyContext.Provider>
. Вот сигнатура вызова:

Context в React инициализируется с помощью API верхнего уровня createContext
. Пример:

Функция createContext
принимает начальное значение по умолчанию, если не определен проп value
. Можно использовать ее в примере компонента Book:

Хук useState
принимает Context в качестве параметра, чтобы извлечь из него value
. Теперь вышеупомянутый компонент Book
из useContext
получится таким:

3. Хук useReducer
Хук
useReducer
— альтернативаuseState
. Обычно его используют для сложной логики состояния, включающей несколько уровней значений или при зависимости текущего состояние от предыдущего.
Сигнатура вызова:

Он принимает редуктор типа (state, action) => newState
и возвращает текущее состояние в паре с методом dispatch
. Пример использования этого хука:

Инициализировать состояние можно передачей функции init
в качестве третьего аргумента. Возвратом из этой функции будет объект state. Пример для иллюстрации:

4. Хук useCallback
Хук
useCallback
используется для возврата мемоизированной версии переданного обратного вызова (callback), который меняется только в случае изменения одной из зависимостей.
Его удобно использовать при передаче обратных вызовов оптимизированным дочерним компонентам, чтобы избежать ненужного рендера. Сигнатура вызова:

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

Здесь дочерний компонент <Instructions />
имеет проп doSomething
, который передается при вызове хука useCallback
.
5. Хук useMemo
Хук
useMemo
позволяет мемоизировать сложные функции, чтобы не вызывать их при каждом рендере.
После передачи функции “create” и массива зависимостей получим мемоизированное значение. Сигнатура вызова этого хука:

При использовании хука useMemo
следует учитывать следующие особенности:
- Мемоизированное значение будет пересчитано только в случае изменения одной из переданных зависимостей.
- В этот вызов нельзя добавлять побочные эффекты — они должны принадлежать хуку
useEffect
. - В процессе начального рендеринга
useMemo
вызываетcompute
, мемоизирует результат и возвращается в свой компонент. - Если не предоставлять массив, новое значение будет вычисляться при каждом рендере компонента, в котором используется
useMemo
.
Допустим, имеется компонент <CalculateFactorial />
:

Можно мемоизировать вычисление факториала при каждом повторном рендере компонента с помощью useMemo(() => factorialOf(number), [number])
, как показано ниже в обновленном коде:

6. Хук useRef
Хук
useRef
возвращает мутабельный объектref
со свойством.current
, которое инициализировано переданным аргументом(initialValue)
.
Он имеет следующую сигнатуру вызова:

Следует учитывать очень важные особенности useRef
:
useRef
действует как “ящик”, способный хранить изменяемое значение в своем свойстве.current
.- Этот хук создает обычный объект JavaScript. Разница между
useRef()
и созданием объекта{ current: … }
заключается в том, чтоuseRef
дает один и тот же объектref
при каждом рендере. - Если изменить свойство
.current
, это не вызовет повторного рендера. Хук не уведомляет об изменении своего содержимого. - Значение ссылки сохраняется, т. е. остается неизменным при повторном рендере компонента.
- Ссылки также могут обращаться к элементам DOM. Для этого нужно обратиться к атрибуту
ref
элемента, к которому нужен доступ.
Вот пример, в котором нужно получить доступ к элементу DOM, чтобы сфокусироваться на поле ввода при монтировании компонента:

Ссылка на элемент ввода в inputRef
присваивается после создания атрибуту ref
поля ввода. После монтирования React устанавливает inputRef.current
в качестве элемента ввода.
Шпаргалка по хукам React: продвинутый уровень
1. Тестирование хуков React
Если не нужно тестировать внутренние компоненты React, то тестирование компонентов с хуками ничем не отличается от обычного.
Предположим, имеется компонент для подсчета нажатий на кнопку:

Сократить шаблонный код позволяет тестирование с помощью React DOM или библиотеки React Testing Library. Но давайте посмотрим, как это сделать с помощью базового кода. Чтобы убедиться в соответствии поведения происходящему в браузере, обернем рендеринг кода и обновление в несколько вызовов ReactTestUtils.act()
:

2. Получение данных с помощью хуков React
Получение данных — довольно типичный паттерн при работе с приложениями React. Но как получать данные с помощью хуков в конкретном компоненте приложения.
Допустим имеется базовый компонент App
, который показывает список элементов:

И состояние, и функция обновления состояния приходят из хука useState
. Для получения данных воспользуемся библиотекой Axios. Реализуем эффект хука:

Но запустив приложение вы увидите, что код крутится в цикле. Эффект хука запускается и при монтировании, и при обновлении компонента. Мы устанавливаем состояние после каждой выборки данных. Чтобы исправить это, необходимо получать данные только при монтировании компонента. Поэтому нужно предоставить пустой массив в качестве второго аргумента хука useEffect
:

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

3. Создание с помощью хуков индикатора загрузки
Базовый индикатор загрузки реализуем на основе использованного выше примера загрузки данных. Для этого добавим еще одно значение состояния с помощью хука useState
:

Внутри вызова useEffect
можно установить переключатель значений true
и false
для setIsLoading
:

При вызове эффекта для получения данных, когда компонент монтируется или изменяется состояние URL, состояние загрузки устанавливается в true:

4. Прерывание выборки данных с помощью хуков
Часто установленное состояние имеется даже у смонтированного компонента. Чтобы не столкнуться в этом случае с ошибками или циклами, нужно прервать получение данных в компоненте.
Можно использовать эффект хука с функцией очистки, запускаемый при размонтировании компонента. Воспользуемся этим вариантом для предотвращения выборки данных:

Мы использовали булевое значение didCancel
, чтобы код извлечения данных знал о состоянии компонента (смонтирован/ размонтирован). Если оно размонтировано, флаг устанавливается в true
, что предотвращает установку состояния компонента после разрешения асинхронной выборки данных.
5. Получение предыдущих пропов или состояний с хуками
Для очистки используемого эффекта иногда нужны предыдущие пропы компонента. Для иллюстрации представим эффект с подпиской на сокет на основе пропа userId
. Если он меняется, мы хотим отписаться от ранее установленного userId
, а затем подписаться на другой. Для этого можно использовать функцию очистки с функцией useEffect
:

Сначала будет запущен ChatAPI.unsubscribeFromSocket(3)
, а затем — ChatAPI.unsubscribeFromSocket(4)
(в порядке очереди при изменении userId
). Таким образом, функция очистки делает всю работу за нас и закрывает предыдущий userId
.
Заключение
В этой шпаргалке по хукам React рассмотрены и проиллюстрированы соответствующими примерами все главные особенности работы с хуками — начиная с их назначения и заканчивая дополнительными возможностями эффективного управления состоянием приложения.
Читайте также:
- Почему в React важен порядок вызова хуков?
- Топ-10 инструментов, которые понадобятся каждому React-разработчику в 2024 году
- Проблема устаревших замыканий и способы ее решения в React. Часть 1
Читайте нас в Telegram, VK и Дзен
Перевод статьи Adarsh Rai: React Hooks Cheat Sheet