Pull to refresh

Введение в React Hooks

Reading time12 min
Views101K
Original author: Yomi Eluwande, Nate Murray


Если вы читаете Twitter, вы, вероятнее всего, знаете что Hooks  -  это новая фича React, но вы можете спросить, как мы на практике можем их использовать? В этой статье мы покажем вам несколько примеров использования Hooks.
Одна из ключевых идей для понимания заключается в том, что Hooks позволяют использовать state и другие возможности React без написания классов.

Мотивация стоящая за Hooks


Хотя компоненто-ориентированная архитектура позволяет нам повторно использовать view в нашем приложении, одна из самых больших проблем, с которыми сталкивается разработчик, заключается в том, как повторно использовать логику, находящуюся в state, между компонентами. Когда у нас есть компоненты, которые имеют сходную логику состояния, нет хороших решений для переиспользования компонентов, и это иногда может привести к дублированию логики в конструкторе и методах жизненного цикла.
Чтобы решить эту проблему, обычно используют:

  • компоненты высшего порядка (High Order Components)
  • render props

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

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

Hooks в Alpha


Прежде чем мы погрузимся, важно упомянуть, что разработка Hooks API еще не завершена.

Кроме того, официальная документация очень хороша, и мы рекомендуем ее прочитать еще и потому, что в ней расширенно описана мотивация, стоящая за введением Hooks.
UPD Оригинальная статья, перевод которой вы читаете была написана еще в то время, когда это API было в alpha тестировании, на данный момент React Hooks официально готовы к использованию. Необратимые изменения, привнесенные в релиз (по сравнению с альфой) можно посмотреть внизу статьи или в релизных заметках.

Как Hooks соотносятся с классами


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

Напомним, что при написании классов компонентов нам часто необходимо:

  • Управлять state
  • Использовать методы жизненного цикла, такие как componentDidMount() и componentDidUpdate()
  • Доступ к context (static contextType)


С помощью React Hooks мы можем воспроизвести аналогичное поведение в функциональных компонентах:

  • Для доступа к состоянию компонента использовать useState() hook
  • Вместо использования методов жизненного цикла таких как componentDidMount() и componentDidUpdate(), использовать useEffect() hook.
  • Вместо static свойства contextType использовать useContext() hook.

Для использования Hooks необходима последняя версия React


Вы можете начать работу с Hooks прямо сейчас, сменив значение react и react-dom в вашем package.json на «next».



Пример useState() Hook


State является неотъемлемой частью React. Он позволяет нам объявлять переменные, которые содержат данные, которые, в свою очередь, будут использоваться в нашем приложении. С помощью классов state обычно определяется следующим образом:



До Hooks, state обычно использовался только в компоненте - классе, но, как упоминалось выше, Hooks позволяет нам добавлять состояние и к функциональному компоненту.
Давайте посмотрим пример ниже. Здесь мы построим переключатель с подсветкой, который изменит цвет в зависимости от значения state. Для этого мы будем использовать hook useState().
Вот полный код (и исполняемый пример) - мы рассмотрим, что происходит ниже. По клику на изображение можно посмотреть на этот пример на CodeSandBox.



Наш компонент - это функция


В вышеприведенном блоке кода, мы начинаем с импорта useState из React. UseState  —  это новый способ использования возможностей, которые раньше могло предложить this.state.
Затем обратите внимание, что этот компонент является функцией, а не классом. Интересно!

Чтение и запись state


Внутри этой функции мы вызываем useState для создания переменной в state:



useState используется для объявления переменной state и может быть инициализирован любым типом значения (в отличие от state в классах, который должен быть объектом).

Как видно выше, мы используем деструктуризацию по возвращаемому значению useState.
  • Первое значение, light в этом случае, является текущим state (как this.state)
  • Второе значение - это функция, используемая для обновления значения state(первого значения) (как this.setState).

Затем мы создаем две функции, каждая из которых устанавливает state в разные значения, 0 или 1.



Затем мы применяем их как обработчики событий на кнопках во view:



React отслеживает state


Когда нажимается кнопка «On», Вызывается функция setOn, вызывающая setLight(1). Вызов setLight(1) обновляет значение light для следующего рендера. Это может показаться немного волшебным, но React отслеживает значение этой переменной и будет передавать новое значение, когда происходит ре-рендер этого компонента.
Затем мы используем текущее состояние (light), чтобы определить, должна ли лампа быть включена или нет. То есть, мы устанавливаем цвет заливки SVG в зависимости от значения light. Если light равен 0 (выкл.), То для fillColor установлено значение # 000000 (а если равен 1 (включено), fillColor устанавливается на # ffbb73).

Multiple States


Хотя мы не делаем этого в приведенном выше примере, вы можете создать несколько state, вызвав useState более одного раза. Например:



ПРИМЕЧАНИЕ. 
Существуют некоторые ограничения при использовании hooks, о которых вы должны знать. Самое главное, вы должны вызывать hooks только на верхнем уровне вашей функции. См. «Правила hooks» для получения дополнительной информации.


Пример useEffect() Hook


UseEffect Hook позволяет выполнять side эффекты в функциональных компонентах. Побочными эффектами могут быть обращения к API, обновление DOM, подписка на обработчики событий - все, что хотите, лишь бы произошло «императивное» действие.

Используя useEffect() Hook, React знает, что вы хотите выполнить определенное действие после рендеринга.

Давайте посмотрим на пример ниже. Мы будем использовать useEffect() для вызова API и получения ответа.



В этом примере кода используются как useState, так и useEffect, и это потому, что мы хотим записать результат вызова API в state.



Получение данных и обновления state


Чтобы «использовать эффект», нам нужно поместить наш action в функцию useEffect, то есть мы передаем «action» эффект как анонимную функцию, как первый аргумент useEffect.
В примере выше мы обращаемся к API, которое возвращает список имен. Когда возвращается response, мы конвертируем его в JSON, а затем используем setNames(data) для установки state.



Проблемы с производительностью при использовании Effects



Однако стоит сказать еще кое-что об использовании useEffect.

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

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

В приведенном выше примере кода обратите внимание, что мы передаем пустой массив в качестве второго аргумента. Это мы говорим React, что мы хотим только назвать этот effect при монтировании компонента.

Чтобы узнать больше о производительности effect, ознакомьтесь с этим разделом в официальных документах.

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

Пример useContext() Hook


Точка контекста

Контекст в React- это способ для дочернего компонента получить доступ к значению в родительском компоненте.

Чтобы понять необходимость context: при создании React приложения вам часто нужно передавать значения с верха вашего дерева React вниз. Не используя context, вы передаете props через компоненты, которым не обязательно о них знать.

Передача props вниз по дереву «несвязанных» компонентов ласково называется props drilling.
React Context решает проблему props drilling, позволяя вам делиться значениями через дерево компонентов, с любым компонентом, который запрашивает эти значения.

useContext() упрощает использование context

С useContext Hook использование context стает проще, чем когда-либо.

Функция useContext() принимает объект сontext, который изначально возвращается из React.createContext(), а затем возвращает текущее значение контекста. Давайте посмотрим на пример ниже.



В приведенном выше коде context JediContext создается с использованием React.createContext().

Мы используем JediContext.Provider в нашем App компоненте и устанавливаем там значение «Luke». Это означает, что любой компонент, которому нужно получить доступ к context теперь сможет считать это значение.

Чтобы прочитать это значение в функции Display(), мы вызываем useContext, передавая аргумент JediContext.

Затем мы передаем объект context, который мы получили из React.createContext, и он автоматически выводит значение. Когда значение провайдера будет обновляться, этот Hook автоматически сработает с последним значением context.

Получение ссылки на context в более крупном приложении


Выше мы создали JediContext в рамках обоих компонентов, но в более крупном приложении Display и App будут находиться в разных файлах. Поэтому, если у вас похожая ситуация, вам может быть интересно: «Как мы получаем ссылку на JediContext между файлами?»

Ответ заключается в том, что вам нужно создать новый файл, который экспортирует JediContext.
Например, у вас может быть файл context.js, который содержит что-то вроде этого:



и потом в App.js (и Display.js) вы должны написать:



Спасибо, Дейв)

Пример useRef() Hook


Refs предоставляет способ доступа к React элементам , созданным в методе render().
Если вы новичок в React refs, вы можете прочитать это введение в React refs.
Функция useRef() возвращает объект ref.



useRef() и формы с input


Давайте посмотрим пример использования useRef() hook.



В приведенном выше примере мы используем useRef() в сочетании с useState(), чтобы отрендерить значение input в тег p.

Ref создается в переменной nameRef. Затем переменную nameRef можно использовать в input, задав как ref. По существу, это означает, что теперь содержимое поля ввода будет доступно через ref.

Кнопка отправки в коде имеет обработчик события onClick, называемый submitButton. Функция submitButton вызывает setName (созданный через useState).

Как мы уже делали с использованием hookState, setName будет использоваться для установки state name. Чтобы извлечь имя из тега input, мы читаем значение nameRef.current.value.

Еще одно замечание относительно useRef заключается в том, что его можно использовать больше, чем атрибут ref.

Использование пользовательских Hooks


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

В приведенном ниже примере мы создадим пользовательский setCounter() Hook, который позволяет нам отслеживать состояние и предоставлять настраиваемые функции обновления state!

См. Также, этот useCounter Hook от react-use и useCounter от Кента




В приведенном выше блоке кода мы создаем функцию useCounter, которая хранит логику нашего hook.

Обратите внимание, что useCounter может использовать другие Hooks! Начнем с создания нового состояния Hook через useState.

Затем мы определяем две вспомогательные функции: increment и decrement, которые вызывают setCount и соответственно корректируют текущий count.

Наконец, мы возвращаем ссылки, необходимые для взаимодействия с нашим Hook.

В: Что происходит, возврат массива с объектом?
О: Ну, как и большинство вещей в Hooks, соглашения API еще не завершены. Но то, что мы делаем здесь, возвращает массив, где:

  • Первый элемент - текущее значение Hook
  • Второй элемент - это объект, содержащий функции, используемые для взаимодействия с Hook.

Это соглашение позволяет вам легко «переименовать» текущее значение Hook - как мы делаем выше с помощью myCount.

Тем не менее, вы можете вернуть все, что захотите, из своего кастомного Hook.

В приведенном выше примере мы используем increment и decrement как обработчики onClick, в нашем view. Когда пользователь нажимает кнопки, счетчик обновляется и повторно отображается (как myCount) во view.

Написание тестов для React Hooks


Чтобы написать тесты для hooks, мы будем использовать библиотеку для тестирования react-testing-library.

react-testing-library - очень легковесное решение для тестирования компонентов React. Она является раширением react-dom and react-dom/test-utils. Использование библиотеки react-testing-library гарантирует, что ваши тесты будут работать непосредственно с DOM узлами.

С тестированием hooks еще не все понятно. В настоящее время вы не можете протестировать hook изолированно. Вместо этого вам нужно прикрепить свой hook к компоненту и протестировать этот компонент.

Итак, ниже мы будем писать тесты для наших Hooks, взаимодействуя с нашими компонентами, а не с Hooks напрямую. Хорошая новость заключается в том, что наши тесты будут выглядеть как обычные тесты React.

Тестирование useState() Hook


Давайте посмотрим пример написания тестов для useState Hook. В приведенном выше уроке мы тестируем больше вариаций используемого выше примера useState. Мы будем писать тесты, чтобы убедиться, что нажатие кнопки «Off» Устанавливает состояние в 0 и нажатие кнопки «On» Устанавливает состояние в 1.



В вышеприведенном блоке кода мы начинаем с импорта некоторых хелперов из react-testing-library и тестируемого компонента.

  • render, это поможет отобразить наш компонент. Он рендерится в контейнер, который добавляется к document.body
  • getByTestId получает DOM элемент по data-testid
  • fireEvent, это используется для «запуска» событий DOM. Она прикрепляет обработчик события к document и обрабатывает некоторые события DOM через делегирование событий, например. нажав кнопку.

Далее, в функции утверждения теста, мы задаем значения переменных для элементов с data-testid и их значения, которые мы хотели бы использовать в тесте. Со ссылками на элементы на DOM мы можем затем использовать метод fireEvent для имитации щелчка по кнопке.

Тест проверяет, что, если нажимается onButton, значение state устанавливается в 1, а при нажатии на offButton state равен 1.

Тестирование useEffect() Hook 


В этом примере мы будем писать тесты, чтобы добавить товар в корзину, используя useEffect Hook. Количество элементов также сохраняется в localStorage. Файл index.js в CodeSandbox ниже содержит фактическую логику, используемую для добавления элементов в корзину.

Мы будем писать тесты, чтобы убедиться, что обновление количества элементов корзины также отражено в localStorage, и даже если страница перезагрузилась, количество элементов корзины все еще остается прежним.



В функции, подтверждающей прохождение теста мы сначала устанавливаем cartItem в localStorage равным 0, что означает, что количество элементов корзины равно 0. Затем мы получаем как container так и rerender из компонента App через деструктурирование. Rerender позволяет нам имитировать перезагрузку страницы.

Затем мы получаем ссылки на кнопки и тег p, который отображает текущее значение корзины и устанавливает их в переменные.

Как только это будет сделано, тест затем имитирует щелчок на addButton и проверяет, является ли текущий счетчик корзины равным 1 и перезагружает страницу, после чего, если он проверяет, установлено ли значение localStorage, cartItem, равным 1. Затем он моделирует нажатие на resetButton и проверяет, установлено ли текущее количество элементов корзины равным 0.

Тестирование useRef () Hook


В этом примере мы будем тестировать useRef Hook, и мы будем использовать исходный пример useRef, приведенный выше в качестве основы для теста. UseRef используется для получения значения из поля ввода, а затем устанавливает значение state. Файл index.js в CodeSandbox ниже содержит логику ввода значения и его отправки.



В функции, утверждающей прохождение теста мы устанавливаем переменные в поле input, тег p, который отображает текущее значение ref, и кнопку отправки. Мы также устанавливаем значение, которое мы хотели бы ввести в поле ввода, для переменной newName. Это будет использоваться для проверки в тесте.



Метод fireEvent.change используется для ввода значения в поле input, и в этом случае используется name, сохраненное в константе newName, после чего нажимается кнопка отправки.

Затем тест проверяет, соответствует ли значение ref после нажатия кнопки значение newName.

Наконец, вы должны увидеть «Нет падений тестов, поздравляем!» сообщение в консоли.

Реакция сообщества на Hooks


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

  • На этом сайте собрана коллекция React Hooks.
  • react-use, библиотека, которая поставляется с кучей React Hooks.
  • В этом примере на CodeSandbox показано, как использовать useEffect Hook для создания анимаций с помощью react-spring
  • Пример использования useMutableReducer, который позволяет вам просто мутировать состояние, чтобы обновить его в редьюсере.
  • Этот пример на CodeSandbox, который показывает сложное комплексное использования связи родитель-ребенок и использование редьсеров.
  • Компонент переключения, построенный с помощью React Hooks
  • Другая коллекция React Hooks, в которой есть hooks для входных значений, ориентации устройств и видимости документа.


Различные типы hooks


Существуют различные типы hooks, которые вы можете начать использовать в своем React приложении. Они перечислены ниже:

  • useState  —  позволяет нам писать чистые функции с доступом к state в них.
  • useEffect  —  позволяет нам выполнять side эффекты. Side эффектами могут быть вызовы API, обновление DOM, подписка на обработчики событий.
  • useContext  -  позволяет писать в них чистые функции с контекстом.
  • useReducer  —  дает нам ссылку на Redux-подобный редьюсер
  • useRef  —  позволяет писать чистые функции, которые возвращают изменяемый объект ref.
  • useMemo  -  используется для возврата сохраненного значения.
  • useCallback —  Hook используется для возврата мемоизованного каллбека.
  • useImperativeMethods  -  кастомизирует значение экземпляра, которое передается родительским компонентам при использовании ref.
  • useMutationEffects  —  аналогичен useEffect Hook в том смысле, что он позволяет выполнять DOM-мутации.
  • useLayoutEffect  -  используется для чтения макета из DOM и синхронного ре-рендеринга.
  • Пользовательские hooks  -  позволяют писать компонентную логику в функции многократного использования.


Будущее hooks


Самое замечательное в Hooks заключается в том, что они работают бок о бок с существующим кодом, поэтому вы можете медленно вносить изменения, внедряющие Hooks. Все, что вам нужно сделать, - это обновить React до версии поддерживающей hooks.

Тем не менее, Hooks все еще являются экспериментальной функцией, и команда React неоднократно предупреждала, что API может быть изменен. Считайте что вы предупреждены.
Что означает для классов появление Hooks? Как сообщает команда React, классы все еще остаются, они являются огромной частью кодовой базы React и, скорее всего, будут еще какое-то время.

У нас нет планов осуждать классы. В Facebook у нас есть десятки тысяч компонентов, написанных классами, и, как и вы понимаете, мы не собираемся переписывать их. Но если сообщество React одобрит Hooks, нет смысла иметь два разных рекомендуемых способа записи компонентов —  Дэн Абрамов

Хотя конкретный API-интерфейс Hooks является экспериментальным сегодня, сообщество одобряет идею Hooks, поэтому я думаю, что они останутся с нами надолго.

Дополнительные ресурсы


  • Команда React сделала замечательную работу по документированию React Hooks, подробнее можете ознакомиться здесь
  • API из официальной документации здесь.
  • Есть также постоянный RFC, поэтому вы можете отправиться туда, чтобы задавать вопросы или оставлять комментарии


UPD
Примечание преводчика:
Сегодня как вызнаете зарелизили версию React 16.8 официально поддерживающую Hooks API. В чейнджлоге указаны следующие несовместимые изменения с альфа-версией:
— удалены useMutationEffect.
— переименованы useImperativeMethods в useImperativeHandle.
— удалось избежать перерендера при передаче обязательных значений в useState и useReducer Hooks.
— не нужно проводить сравнение первого аргумента, переданного в useEffect/useMemo/useCallback Hooks.
— используйте Object.is алгоритм для сравнения значений useState и useReducer.
— компоненты рендерятся дважды в Strict Mode (DEV-only).
— улучшено lazy initialization API для useReducer Hook.
Подробнее можно почитать здесь.
Tags:
Hubs:
Total votes 16: ↑14 and ↓2+12
Comments36

Articles