S.Tominoff

Fullstack JavaScript разработчик

React hooks for little ones, дополненное издание

Гайд с картинками по хукам React для новичков

Предисловие

В 2020-2021 годах я сделал небольшую серию постов React hooks for little ones в своём Instagram аккаунте @object.keys.

Поскольку компания Meta была признана экстремистской организацией в Российской Федерации, а сам ИГ заблокирован, я решил перетащить оттуда свои старенькие посты на свой сайт — возможно этот гайд кому–то окажется полезен.👍

 

ЗЫ: залетай ко мне в телеграм — https://t.me/tocodes

useState

 

Это самый простой хук в React. С его помощью мы можем назначать и управлять состоянием компонента.

 

Состояние компонента — это что–то вроде набора свойств и данных, которые существуют для каждого отдельно взятого компонента. Например, если мы создаём компонент форму, то данные заполненных полей мы будем хранить в состоянии этого компонента.

 

 

Вызов useState вернёт нам массив из двух элементов — в первом будет храниться текущее состояние, во втором — функция, вызвав которую мы можем обновить это состояние.

 

Зачастую, желательно чтобы начальное состояние содержало в себе что–то полезное.

 

Например, состояние индикации загрузки, мы, вероятно, захотели бы инициализировать со значением true, а значение текстового поля — пустой строкой.

 

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

 

Полезно знать 😉

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

 

Атомарные обновления

 

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

 

Функция установки состояния не устанавливает новое состояние мгновенно — она лишь сообщает реакту, какое состояние должно быть при следующем рендере компонента.

 

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

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

 

const [ state, setState ] = useState(0)
// state === 0
setState(state + 1)
// state всё ещё 0
setState(state + 1)
// При следующем рендере state будет 1, а не ожидаемое 2!

 

 

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

Сигнатура функции простая — единственным аргументом она получает текущее состояние, а вернуть должна новое:

 

const [ state, setState ] = useState(0)

setState(state => state + 1)
setState(state => state + 1)
// При следующем рендере state будет 2

 

Прилагаю сэндбокс с примером, чтобы посмотреть как это работает, что называется, «в живую»:

 

 

 

 

useEffect

 

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

 

Самый простой случай — когда хук реагирует на каждый рендер компонента. Для этого мы просто вызываем useEffect с функцией обратного вызова в качестве аргумента (функция–эффект) — эта функция и будет вызываться после каждого рендера компонента.

 

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

 

Всё дело в том, что функция–эффект может возвращать ещё одну функцию, так называемую, функцию сброса (cleanup), которая будет вызвана перед откреплением (unmount) компонента.

 

В качестве примера, мы можем при каждом рендере устанавливать обработчик события и удалять его перед каждым откреплением компонента:

 

 

Как несложно понять, useEffect с единственным аргументом не так уж и полезен — гораздо чаще этот хук используется с двумя аргументами — функцией–эффектом и массивом зависимостей.

 

Массив зависимостей (dependencies array) — это в прямом смысле массив переменных, при изменении любой из которых будет вызван наш эффект.

 

⚠️ Частный случай — пустой массив зависимостей.

Использование пустого массива позволит выполнить эффект только при первом рендере компонента (аналог componentDidMount).

 

В остальных случаях мы можем следить за изменением состояния, пропсов, коллбэков и мемоизированных данных (useCallback/useMemo будут рассмотрены дальше), и каким–то образом реагировать на них.

 

 

Функция–эффект позволяет нам выполнять асинхронный код, но будьте внимательны — в useEffect нельзя передавать async функцию напрямую!

 

Это ограничение исходит из того факта, что React ожидает возврата функции–сброса или undefined из эффекта, а async функции под капотом возвращают объект Promise!

 

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

 

 

 

useLayoutEffect

 

Особняком стоит упомянуть useLayoutEffect.

Этот хук также как и useEffect предназначен для вызова функций–эффектов, но между ними есть два существенных отличия:

 

  1. useLayoutEffect вызывается до useEffect
  2. useLayoutEffect вызывается СИНХРОННО

 

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

 

Вообще, useLayoutEffect используют нечасто, да и команда разработчиков React рекомендует стараться использовать useEffect вместо него.

 

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

 

 

Обрати внимание 😉

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

Я рекомендую прислушаться к совету разработчиков React и использовать этот хук максимально осторожно и только в исключительных ситуациях.

 

 

useMemo

 

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

 

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

 

 

useCallback

 

Этот хук побратим useMemo, но вместо мемоизации результата выполнения, кэширует саму функцию.

 

Зачем это может понадобиться?

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

 

Это наиболее актуально при использовании мемоизированных компонентов, созданных с помощью React.memo.

 

useRef

 

Хук useRef возвращает нам некую структуру, которую называют рефом (ссылкой). Структурно реф это просто объект с одним полем current, в котором содержатся некоторые данные.

 

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

 

 

Однако, помимо работы с DOM, хук useRef можно использовать для других целей.

 

В некоторых случаях, можно рассматривать как некую альтернативу useState, но с одним важным отличием — изменения в рефах не вызывают ререндер компонента!

 

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

 

К примеру, рефы могут оказаться весьма неплохим подспорьем при работе с Drag&Drop интерфейсами — пока пользователь перетаскивает элемент, все промежуточные состояния мы можем сохранять в реф объект, а после отпускания — записывать всё в состояние, тем самым вызывая ререндер только один раз.