Шпаргалка по хукам 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 следует соблюдать два основных правила.

  1. Вызывайте хуки на верхнем уровне. Другими словами: не вызывайте их внутри циклов, условий или вложенных функций.

Гарантированный таким образом вызов хуков каждый раз будет происходить в одном и том же порядке при рендере компонента. Это поможет сохранить состояние приложения между несколькими вызовами useState и useEffect.

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

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

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

React не знает, что возвращать при втором вызове useState. Поэтому несколько пропущенных хуков вызвали проблемы.

2. Вызывайте хуки только из функций React. Другими словами: не вызывайте хуки из обычных функций JavaScript.

Следуя этому правилу, мы гарантируем хорошую наглядность кода логики с состоянием компонента.

Вместо того вызова хуков из обычных функций, можно:

  • вызывать хуки из компонентов функций React;
  • вызывать хуки из пользовательских хуков.

Шпаргалка по хукам React: средний уровень

1. Создание пользовательских хуков

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

Пользовательские хуки  —  это тип функций с префиксом “use”, которые могут вызывать другие функции.

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

  1. Извлечение пользовательского хука. Пользовательские хуки используются для совместного использования логики между двумя функциями 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 следует учитывать следующие особенности:

  1. Мемоизированное значение будет пересчитано только в случае изменения одной из переданных зависимостей.
  2. В этот вызов нельзя добавлять побочные эффекты  —  они должны принадлежать хуку useEffect.
  3. В процессе начального рендеринга useMemo вызывает compute, мемоизирует результат и возвращается в свой компонент.
  4. Если не предоставлять массив, новое значение будет вычисляться при каждом рендере компонента, в котором используется useMemo

Допустим, имеется компонент <CalculateFactorial />:

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

6. Хук useRef

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

Он имеет следующую сигнатуру вызова:

Следует учитывать очень важные особенности useRef:

  1. useRef действует как “ящик”, способный хранить изменяемое значение в своем свойстве .current.
  2. Этот хук создает обычный объект JavaScript. Разница между useRef() и созданием объекта { current: … } заключается в том, что useRef дает один и тот же объект ref при каждом рендере.
  3. Если изменить свойство .current, это не вызовет повторного рендера. Хук не уведомляет об изменении своего содержимого. 
  4. Значение ссылки сохраняется, т. е. остается неизменным при повторном рендере компонента.
  5. Ссылки также могут обращаться к элементам 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 рассмотрены и проиллюстрированы соответствующими примерами все главные особенности работы с хуками  —  начиная с их назначения и заканчивая дополнительными возможностями эффективного управления состоянием приложения.

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи Adarsh Rai: React Hooks Cheat Sheet

Предыдущая статьяПонимание и реализация смарт-указателя Arc и мьютекса на Rust
Следующая статьяGoFr и Gin: сравнительное исследование