3.12.5 Правила использования хуков
Хуки доступны в версии React 16.8. Они позволяют использовать состояние и другие функции React, освобождая от необходимости писать класс.
Хуки являются обычными функциями JavaScript, но при их использовании необходимо следовать двум правилам. Мы предоставляем ESLint плагин, который применяет эти правила автоматически:
Вызывайте хуки только на верхнем уровне
Не вызывайте хуки внутри циклов, условий или вложенных функций. Всегда используйте хуки
только на самом верхнем уровне вашего компонента-функции React. Следуя этому правилу, вы гарантируете,
что хуки вызываются в одном и том же порядке каждый раз при отрисовке компонента. Это именно то, что
позволяет React правильно сохранять состояние хуков между несколькими вызовами useState
и useEffect
.
(Если вам любопытно, мы подробно поговорим об этом ниже.)
Вызывайте хуки только из компонентов-функций React
Не вызывайте хуки из обычных функций JavaScript. Вместо этого вы можете:
-
✅ Вызывать хуки из компонентов-функций React.
-
✅ Вызывафть хуки из пользовательских хуков (с ними мы познакомимся в следующем разделе).
Следуя этому правилу, вы гарантируете, что вся логика состояния компонента четко видна из его исходного кода.
Мы выпустили ESLint плагин под названием eslint-plugin-react-hooks, который применяет два эти правила. Чтобы его попробовать, нужно добавить плагин в свой проект:
npm install eslint-plugin-react-hooks
// Ваша конфигурация ESLint
{
"plugins": [
// ...
"react-hooks"
],
"rules": {
// ...
"react-hooks/rules-of-hooks": "error"
}
}
В будущем мы намерены добавить этот плагин в приложение «Create React App» (и другие подобные инструменты) по умолчанию.
Вы прямо сейчас можете перейти к следующему разделу, который объясняет, как писать свои собственные хуки. Здесь же мы объясним причины таких правил.
Как мы узнали ранее, мы можем использовать несколько хуков состояния или эффекта в одном компоненте:
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);
}
});
Обратите внимание, что вам не нужно беспокоиться об этой проблеме, если вы используете предоставленный ESLint плагин. Плюс ко всему вы теперь знаете, почему хуки работают именно так, и от каких проблем вас уберегает данное правило.
Наступил момент, когда мы готовы узнать, как можно написать свои собственные хуки! Пользовательские хуки позволяют комбинировать хуки, предоставляемые React, в свои собственные абстракции и повторно использовать общую логику состояния между различными компонентами.