3.12.5 Правила использования хуков


Хуки доступны в версии React 16.8. Они позволяют использовать состояние и другие функции React, освобождая от необходимости писать класс.


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


Вызывайте хуки только на верхнем уровне


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


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


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

  • ✅ Вызывать хуки из компонентов-функций React.

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

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



3.12.5.1 ESLint плагин


Мы выпустили 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» (и другие подобные инструменты) по умолчанию.

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



3.12.5.2 Почему возникли эти правила?


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


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



3.12.5.3 Следующие шаги


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