Как использовать хук
Перейти к содержимому

Как использовать хук

  • автор:

Правила хуков

Хуки — нововведение в React 16.8, которое позволяет использовать состояние и другие возможности React без написания классов.

Хуки — обычные JavaScript-функции, но существует два правила, которым нужно следовать. Чтобы автоматически их применять мы создали плагин для линтера:

Используйте хуки только на верхнем уровне

Не вызывайте хуки внутри циклов, условных операторов или вложенных функций. Вместо этого всегда используйте хуки только внутри React-функций, до возврата какого-либо значения из них. Исполнение этого правила гарантирует, что хуки вызываются в одинаковой последовательности при каждом рендере компонента. Это позволит React правильно сохранять состояние хуков между множественными вызовами useState и useEffect . (Если вам интересно, подробное объяснение ниже.)

Вызывайте хуки только из React-функций

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

  • ✅ Вызывать хуки из функционального компонента React.
  • ✅ Вызывать хуки из пользовательского хука (мы научимся делать это на следующей странице).

Следуя этому правилу, можно гарантировать, что вся логика состояния компонента чётко видна из исходного кода.

Плагин для ESLint

Мы выпустили плагин для ESLint eslint-plugin-react-hooks , который принуждает к соблюдению этих двух правил. Если хотите испытать его в деле, добавьте этот плагин в ваш проект.

Этот плагин включен по умолчанию в Create React App.

npm install eslint-plugin-react-hooks --save-dev
// Конфигурация ESLint  "plugins": [ // . "react-hooks" ], "rules":  // . "react-hooks/rules-of-hooks": "error", // Проверяем правила хуков "react-hooks/exhaustive-deps": "warn" // Проверяем зависимости эффекта > >

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

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

function Form()  // 1. Используем переменную состояния name const [name, setName] = useState('Мэри'); // 2. Используем эффект для сохранения данных формы useEffect(function persistForm()  localStorage.setItem('formData', name); >); // 3. Используем переменную состояния surname const [surname, setSurname] = useState('Поппинс'); // 4. Используем эффект для обновления заголовка страницы useEffect(function updateTitle()  document.title = name + ' ' + surname; >); // . >

Итак, как же React сопоставляет переменные состояния с вызовами useState ? Ответ таков: React полагается на порядок вызова хуков. Наш пример работает, потому что порядок вызова хуков одинаков при каждом рендере.

// ------------ // Первый рендер // ------------ useState('Мэри') // 1. Инициализируем переменную name значением 'Мэри' useEffect(persistForm) // 2. Добавляем эффект для сохранения данных формы useState('Поппинс') // 3. Инициализируем переменную surname значением 'Поппинс' useEffect(updateTitle) // 4. Добавляем эффект для обновления заголовка страницы // ------------- // Второй рендер // ------------- useState('Мэри') // 1. Читаем переменную состояния name (аргумент игнорируется) useEffect(persistForm) // 2. Заменяем эффект сохранения данных формы useState('Поппинс') // 3. Читаем переменную состояния surname (аргумент игнорируется) useEffect(updateTitle) // 4. Заменяем эффект обновления заголовка страницы // . 

До тех пор пока порядок вызова хуков одинаков в каждом рендере, React может сопоставить некое внутреннее состояние с каждым из них. Но что случится, если мы поместим вызов хука (например, эффект persistForm ) внутрь условного оператора?

 // �� Нарушаем первое правило, помещая хук в условие if (name !== '')  useEffect(function persistForm()  localStorage.setItem('formData', name); >); >

Условие name !== » равняется true при первом рендере, поэтому хук выполняется. Тем не менее, при следующем рендере пользователь может обратить это условие в false , очистив поля формы. Теперь во время рендера хук будет пропущен и порядок вызовов хуков изменится.

useState('Мэри') // 1. Читаем переменную состояния name (аргумент игнорируется) // useEffect(persistForm) // �� Хук пропускается! useState('Поппинс') // �� 2 (но ранее был 3). Ошибка при чтении переменной состояния surname useEffect(updateTitle) // �� 3 (но ранее был 4). Ошибка при замене эффекта

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

Вот почему хуки должны вызываться на верхнем уровне компонента. Если мы хотим запускать эффект по условию, то можем поместить это условие внутрь хука:

 useEffect(function persistForm()  // �� Первое правило больше не нарушается if (name !== '')  localStorage.setItem('formData', name); > >);

Эта проблема не будет вас беспокоить, если вы включите в свой проект наше правило линтера. Но теперь вы знаете, почему хуки работают таким образом и какие проблемы это правило предотвращает.

Наконец-то мы готовы изучить, как написать свой собственный хук! Пользовательские хуки позволят вам включить собственные хуки React в ваши абстракции и повторно использовать общую логику состояния в различных компонентах.

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

Хуки — нововведение в React 16.8, которое позволяет использовать состояние и другие возможности React без написания классов.

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

import React,  useState > from 'react'; function Example()   // Объявление новой переменной состояния «count» const [count, setCount] = useState(0); return ( div> p>Вы кликнули count> раз(а)p> button onClick=() => setCount(count + 1)>> Нажми на меня button> div> ); >

Давайте начнём изучать хуки, сравнив этот код с эквивалентным кодом на основе класса.

Эквивалентный пример с классом

Если вы уже пользовались классами в React, то вам знаком такой код:

class Example extends React.Component  constructor(props)  super(props); this.state =  count: 0 >; > render()  return ( div> p>Вы кликнули this.state.count> раз(а)p> button onClick=() => this.setState( count: this.state.count + 1 >)>> Нажми на меня button> div> ); > >

Сначала состояние выглядит как < count: 0 >. Каждый раз, когда пользователь кликает, мы увеличиваем state.count на единицу, вызывая this.setState() . Мы будем использовать фрагменты этого класса на протяжении всей страницы.

Примечание

Возможно, вы спросите себя, почему мы используем в качестве примера счётчик, а не что-то более реалистичное. Дело в том, что мы хотим обратить ваше внимание на API, одновременно делая первые шаги с хуками.

Хуки и функциональные компоненты

Напоминаем, что функциональные компоненты в React выглядят так:

const Example = (props) =>  // Тут мог бы быть ваш хук! return div />; >
function Example(props)  // Тут мог бы быть ваш хук! return div />; >

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

Хуки НЕ работают внутри классов, а используются вместо них.

Наш новый пример начинается с того, что импортирует хук useState из React:

import React,  useState > from 'react'; function Example()  // . >

Что такое хук? Хук — это специальная функция, которая позволяет «подцепиться» к возможностям React. Например, хук useState предоставляет функциональным компонентам доступ к состоянию React. Мы узнаем про другие хуки чуть позже.

Когда применить хук? Раньше, если вы писали функциональный компонент и осознавали, что вам нужно наделить его состоянием, вам приходилось превращать этот компонент в класс. Теперь же вы можете использовать хук внутри существующего функционального компонента. Мы покажем это прямо сейчас!

Примечание:

Есть специальные правила о том, где можно, а где нельзя использовать хуки внутри компонента. Их мы изучим в главе Правила хуков.

Объявление переменной состояния

Допустим, мы хотим инициализировать в классе состояние count значением 0 . Для этого в его конструкторе присваиваем this.state объект < count: 0 >:

class Example extends React.Component  constructor(props)  super(props); this.state =  count: 0 >; >

В функциональном компоненте нам недоступен this , поэтому мы не можем задать или считать состояние через this.state . Вместо этого мы вызываем хук useState напрямую изнутри нашего компонента.

import React,  useState > from 'react'; function Example()   // Объявление новой переменной состояния «count» const [count, setCount] = useState(0);

Что делает вызов useState ? Он объявляет «переменную состояния». Мы называли переменную count , но могли дать ей любое имя, хоть банан . Таким образом мы можем «сохранить» некоторые значения между вызовами функции. useState — это новый способ использовать те же возможности, что даёт this.state в классах. Обычно переменные «исчезают» при выходе из функции. К переменным состояния это не относится, потому что их сохраняет React.

Какие аргументы передавать useState ? Единственный аргумент useState — это исходное состояние. В отличие от случая с классами, состояние может быть и не объектом, а строкой или числом, если нам так удобно. Поскольку в нашем примере отслеживается количество сделанных пользователем кликов, мы передаём 0 в качестве исходного значения переменной. (Если нам нужно было бы хранить два разных значения в состоянии, то пришлось бы вызвать useState() дважды.)

Что возвращается из useState ? Вызов useState вернёт пару значений: текущее состояние и функцию, обновляющую состояние. Поэтому мы пишем const [count, setCount] = useState() . Это похоже на this.state.count и this.setState в классах, с той лишь разницей, что сейчас мы принимаем их сразу в паре. Если вам незнаком использованный синтаксис, мы вернёмся к нему ближе к концу страницы.

Теперь мы знаем, что делает useState , и пример должен быть ясен:

import React,  useState > from 'react'; function Example()   // Объявление новой переменной состояния «count» const [count, setCount] = useState(0);

Мы объявляем переменную состояния count и устанавливаем ей значение 0 . React будет помнить текущее (наиболее свежее) значение между рендерингами и передавать его нашей функции. Если мы захотим изменить count , мы вызовем setCount .

Примечание

Может быть, вы спросите себя, почему useState не назвали createState ?

Слово «create» («создать») было бы не совсем точно, потому что состояние создаётся только в момент, когда компонент рендерится впервые. В последующие же рендеринги useState возвращает текущее состояние. Иначе не существовало бы «состояния» как такового. Названия всех хуков начинаются с «use» тоже неспроста. О причине мы узнаем из Правил хуков.

Когда мы хотим отобразить текущее состояние счётчика в классе, мы обращаемся к this.state.count :

 p>Вы кликнули this.state.count> раз(а)p>

В функции же мы можем использовать count напрямую:

 p>Вы кликнули count> раз(а)p>

В классе мы вызываем this.setState() , когда надо обновить состояние count :

 button onClick=() => this.setState( count: this.state.count + 1 >)>>  Нажми на меня button>

В функции нам не нужен this , потому что setCount и count уже доступны как переменные:

 button onClick=() => setCount(count + 1)>>  Нажми на меня button>

Давайте построчно пробежимся по тому, что мы выучили и проверим наши знания:

 1: import React,  useState > from 'react'; 2: 3: function Example()   4: const [count, setCount] = useState(0); 5: 6: return ( 7: div> 8: p>Вы кликнули count> раз(а)p>  9: button onClick=() => setCount(count + 1)>> 10: Нажми на меня 11: button> 12: div> 13: ); 14: >
  • Строка 1: Импортируем хук useState из React. Он позволяет функциональному компоненту хранить внутреннее состояние.
  • Строка 4: Объявляем внутри компонента Example новую переменную состояния, вызвав хук useState . Этот вызов возвращает пару значений, которым мы даём имена. Поскольку наша переменная состояния хранит количество сделанных по кнопке кликов, мы называем её count . Чтобы проинициализировать её, мы передаём значение 0 в качестве единственного аргумента функции useState . Второе возвращённое нам значение позволяет обновлять count , поэтому мы называем её setCount .
  • Строка 9: Когда пользователь кликает по кнопке, мы вызываем setCount с приращённым значением. После этого React сделает повторный рендер, в котором использует уже новое значение count .

Поначалу это всё может показаться слишком сложным. Не торопитесь! Если вы запутались в объяснении, ещё раз прочитайте приведённый код с начала до конца. Обещаем, если вы на минутку «забудете», как состояние работает в классах, и посмотрите на код свежим взглядом, всё станет ясно.

Совет: Что делают квадратные скобки?

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

 const [count, setCount] = useState(0);

Два имени в квадратных скобках не содержатся в API React. Названия переменным состояния выбираете вы:

 const [fruit, setFruit] = useState('банан');

Такой синтаксис в JavaScript называется «деструктуризацией массивов (array destructuring)». Он означает, что мы создаём две новые переменные, fruit и setFruit . Во fruit будет записано первое значение, вернувшееся из useState , а в setFruit — второе, что равносильно такому коду:

 var fruitStateVariable = useState('банан'); // Возвращает пару значений var fruit = fruitStateVariable[0]; // Извлекаем первое значение var setFruit = fruitStateVariable[1]; // Извлекаем второе значение

Когда мы объявляем переменную состояния с помощью функции useState , мы получаем от неё пару, то есть массив из двух элементов. Первый элемент обозначает текущее значение, а второй является функцией, позволяющей менять это значение. Доступ к элементам через [0] и [1] менее ясен, потому что индексы лишены осмысленных имён.

Примечание

Вам может быть любопытно, а как же React знает, какому компоненту соответствует какой вызов useState , если мы не передаём React ни this ни чего-либо подобного. Ответ на этот и многие другие вопросы мы дадим в FAQ.

Совет: Использование нескольких переменных состояния

Объявлять переменные состояния через пару [something, setSomething] удобно ещё и тем, что когда нам нужны несколько переменных, мы можем назвать каждую из них собственным именем:

function ExampleWithManyStates()  // Объявим несколько переменных состояния! const [age, setAge] = useState(42); const [fruit, setFruit] = useState('банан'); const [todos, setTodos] = useState([ text: 'Изучить хуки' >]);

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

 function handleOrangeClick()  // Аналогично коду this.setState(< fruit: 'апельсин' >) setFruit('апельсин'); >

Использовать несколько переменных состояния совсем не обязательно, потому что они могут быть объектами или массивами, которые группируют связанные по смыслу данные. Обратите внимание, что, в отличие от this.setState в классах, обновление переменной состояния всегда замещает её значение, а не осуществляет слияние.

Подробные рекомендации о разделении независимых переменных состояния вы найдёте в FAQ.

На этой странице мы изучили хук React под названием useState . Иногда мы будем ссылаться на него как на «хук состояния». Он позволяет добавить состояние в функциональные компоненты React, в чём мы убедились на примере!

Мы узнали ещё немного больше о хуках — функциях, позволяющих функциональным компонентам «подцепиться» к возможностям React. Их имена всегда начинаются с use . Существует много других хуков, которые мы пока не рассматривали.

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

Хук useContext — JS: React Hooks

React передает данные внутрь компонентов с верхних уровней на нижние, используя пропсы.

Такой подход неудобен при работе с глобальными данными, которые нужны одновременно во многих компонентах на разных уровнях иерархии. К таким данным относится текущий уровень, текущая тема (темная или светлая) и так далее. Напрямую передавать такие данные неудобно, придется протаскивать их сквозь все приложение.

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

Хук useContext() позволяет использовать контекст внутри компонента. Для этого нужно выполнить три действия:

    Инициализировать контекст в том же месте, где инициализируется приложение

// Параметром передается значение по умолчанию // Имя контекста выбирается произвольно const UserContext = React.createContext(<>); 
// user — данные которые лежат внутри контекста UserContext.Provider value=user>> MyComponent /> UserContext.Provider> 
import React,  useContext > from 'react'; const MyComponent = () =>  // Возвращает контекст целиком const user = useContext(UserContext); return h1>user.name>h1>; > 

Вот другой пример контекста, в котором хранится текущая тема:

const themes =  light:  foreground: '#000000', background: '#eeeeee', >, dark:  foreground: '#ffffff', background: '#222222', >, >; const ThemeContext = React.createContext(<>); 

И где-то внутри приложения:

ThemeContext.Provider value=/* текущая тема */>> Content /> ThemeContext.Provider> 

Метод React.createContext() принимает значение по умолчанию. Это значение будет передаваться в контекст тех компонентов, которые не обернуты в провайдер. Обычно провайдер всегда используется, чтобы оборачивать все приложение. Поэтому компоненты всегда принимают в контексте значение, переданное провайдером. Но если мы работаем с компонентом вне провайдера, может понадобиться такое значение по умолчанию. Например, такая ситуация может сложиться при тестировании компонента отдельно от приложения.

Внутри контекста может храниться как примитивное значение, так и объект. Изменение содержимого такого объекта никак не отслеживается React, поэтому не приводит к перерендеру. Но если заменить сам объект, то из-за изменившейся ссылки React узнает об изменении и выполнит перерисовку компонентов внутри провайдера.

Иногда возникает ситуация, когда в контексте нужно хранить динамические данные. Например, при авторизации. Когда пользователь авторизован, мы должны сохранить какие-то данные, чтобы пользователю предоставлялись дополнительные функции. Сам по себе контекст для этого ничего не предоставляет, но можно передать в контекст методы для манипулирования данными, а сами данные хранить с помощью useState() . Более продвинутый вариант — это создать провайдер в отдельном компоненте. Так мы сможем изолировать данные от всего приложения, а в компоненты передавать интерфейс для взаимодействия с данными.

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

// ThemeContext.js import  createContext > from 'react'; export default createContext( themes: <>, theme: <>, setTheme: () => <>, >); 

Создаем отдельный компонент провайдера:

import ThemeContext from './ThemeContext.js'; const themes =  light:  foreground: '#000000', background: '#eeeeee', >, dark:  foreground: '#ffffff', background: '#222222', >, >; const ThemeProvider = ( children >) =>  const [theme, setTheme] = useState(themes.dark); const setLightTheme = () => setTheme(themes.light); const setDarkTheme = () => setTheme(themes.dark); return ( ThemeContext.Provider value= theme, setLightTheme, setDarkTheme, themes >>> children> ThemeContext.Provider> ); >; 

И внутри приложения используем провайдер как обычный компонент:

ThemeProvider> MyComponent /> ThemeProvider> 

В самом компоненте теперь можно импортировать контекст и использовать функции из провайдера для изменения состояния в контексте:

import React,  useContext > from 'react'; import ThemeContext from './ThemeContext.js'; const MyComponent = () =>  const  setLightTheme > = useContext(ThemeContext); return button onClick=() => setLightTheme()>>Включить светлую темуbutton>; > 

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов

Наши выпускники работают в компаниях:

Как использовать хуки внутри jest тестов

Я недавно начал изучать Jest и никак не могу понять, как использовать хуки в тестах Jest. Суть в том, что не работают именно React хуки. При помещении хука в ‘name.test.tsx’ выдает ошибку :

useSubmitAuthor > 1 ----- TypeError: Cannot read properties of null (reading 'useState')Jest 

Хотя я не уверен правильно ли так делать код useInputChange.tsx :

import < ChangeEvent >from 'react'; function useInputChange(setText: (text: string) => void) < if (setText) < return < onChange: (event: ChangeEvent) => setText(event.target.value), >; > return < onChange: () =><> >; > export default useInputChange; 

код useInputChange.text.tsx :

import '@testing-library/jest-dom'; import React, < useState >from 'react'; describe('useInputChange', () => < it('1', () => < const [text, setText] = useState('') // тут ошибка expect(text).toBe('') render(  ); >); >); 

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *