3.12.2 Обзор хуков


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


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


Детальное объяснение

Прочитайте раздел о мотивации, чтобы узнать, почему мы вводим хуки React.

↑↑↑ Каждый раздел заканчивается таким желтым прямоугольником. Он ссылается на подробное объяснение.




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


Код
    
  import { useState } from 'react';

  function Example() {
    // Объявляем новую переменную состояния, которую назовём "count"
    const [count, setCount] = useState(0);
  
    return (
      <div>
        <p>Вы кликнули {count} раз</p>
        <button onClick={() => setCount(count + 1)}>
          Кликни меня!
        </button>
      </div>
    );
  }
  

Здесь useState - это хук (мы сейчас поговорим о том, что это значит). Мы вызываем его внутри компонента-функции, чтобы наделить последний локальным состоянием. React сохранит это состояние между повторными отрисовками. useState возвращает пару: текущее значение состояния и функцию, которая позволяет вам обновлять его. Вы можете вызвать эту функцию из обработчика событий или из любого другого места. Он похож на this.setState в классе, за исключением того, что он не объединяет старое и новое состояние. (Мы покажем пример, сравнивающий useState с this.state в разделе Использование хука состояния.)

Единственный аргумент useState является начальным состоянием. В приведенном выше примере это 0, так как наш счетчик начинается с нуля. Обратите внимание, что в отличие от this.state, состояние здесь не обязательно должно быть объектом. Аргумент начального состояния используется только во время первой отрисовки.


3.12.2.1.1 Объявление множества переменных состояния


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


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

Синтаксис деструктуризации массива позволяет нам давать различные имена переменным состояния, которые мы объявили, вызывая useState. Данные имена не являются частью API useState. Вместо этого React предполагает, что если вы вызываете useState много раз, вы делаете это в том же порядке во время каждой отрисовки. Позже мы еще вернемся к тому, почему это работает, и когда это полезно.


3.12.2.1.2 Но что же такое хук?


Хуки - это функции, которые позволяют вам «зацепить» состояние React и функции жизненного цикла из компонентов-функций. Хуки не работают внутри классов, напротив: они позволяют использовать React без классов. (Мы не рекомендуем в одночасье переписывать ваши существующие компоненты, вместо этого вы можете начать использовать хуки в новых.)



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


Детальное объяснение

Вы можете узнать больше о хуке состояния в разделе Использование хука состояния.




Ранее, скорее всего, выборку данных, подписку или ручное изменение DOM вы выполняли из компонентов React. Мы называем эти операции «побочными эффектами» (или «эффектами» для краткости), так как они могут влиять на другие компоненты и не могут быть выполнены во время отрисовки.

Хук эффекта, по имени useEffect, предоставляет возможность выполнять побочные эффекты из функционального компонента. Он служит тем же целям, что и componentDidMount, componentDidUpdate и componentWillUnmount в классах React, но объединен в единый API. (Мы покажем примеры, сравнивающие useEffect с этими методами в разделе Использование хука эффекта.)

Например, этот компонент установит заголовок документа после того, как React обновит DOM:


Код
    
  import { useState, useEffect } from 'react';
  
  function Example() {
    const [count, setCount] = useState(0);
  
    // Подобен componentDidMount и componentDidUpdate:
    useEffect(() => {
      // Обновление заголовка документа, используя API браузера
      document.title = `Вы кликнули ${count} раз`;
    });
  
    return (
      <div>
        <p>Вы кликнули {count} раз</p>
        <button onClick={() => setCount(count + 1)}>
          Кликни меня
        </button>
      </div>
    );
  }
  

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


Код
    
  import { useState, useEffect } from 'react';
  
  function FriendStatus(props) {
    const [isOnline, setIsOnline] = useState(null);
  
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }
  
    useEffect(() => {
      ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
  
      return () => {
        ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
      };
    });
  
    if (isOnline === null) {
      return 'Загрузка...';
    }
    return isOnline ? 'Онлайн' : 'Офлайн';
  }
  

В этом примере React будет отписываться от нашего ChatAPI перед демотированием компонента, а также перед повторным запуском эффекта, вызванным повторной отрисовкой. (Есть еще один способ указать React пропустить повторную подписку, если props.friend.id, который мы передали ChatAPI, не изменился.)

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


Код
    
  function FriendStatusWithCounter(props) {
    const [count, setCount] = useState(0);
    useEffect(() => {
      document.title = `Вы кликнули ${count} раз`;
    });
  
    const [isOnline, setIsOnline] = useState(null);
    useEffect(() => {
      ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
      return () => {
        ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
      };
    });
  
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }
    // ...
  

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


Детальное объяснение

Вы можете узнать больше о хуке эффекта в разделе Использование хука эффекта.




Хуки - это функции JavaScript, но они накладывают два дополнительных правила:

  • Вызывайте хуки только на верхнем уровне. Не вызывайте хуки внутри циклов, условий или вложенных функций.

  • Вызывайте хуки только в компонентах-функциях React. Не вызывайте хуки из обычных функций JavaScript. (Есть еще одно валидное место, где можно вызывать хук - ваши собственные пользовательские хуки. Мы изучим их чуть позже.)

Мы предоставляем плагин linter для автоматического применения этих правил. Мы понимаем, что на первый взгляд они могут показаться ограничивающими или сбивающими с толку, но они необходимы для правильной работы хуков.


Детальное объяснение

Вы можете узнать больше об этих правилах в разделе Правила использования хуков.




Иногда нам необходимо повторно использовать некоторую логику состояния в различных компонентах. Традиционно существовало два популярных решения этой проблемы: компоненты более высокого порядка и свойство render. Пользовательские хуки позволяют вам достичь этого без добавления дополнительных компонентов в ваше дерево.



Ранее в этом разделе мы представили компонент FriendStatus, который вызывает хуки useState и useEffect для подписки на онлайн-статус друга. Допустим, мы также хотим повторно использовать эту логику подписки в другом компоненте.

Сначала мы извлечем эту логику в пользовательский хук с именем useFriendStatus:


Код
    
  import { useState, useEffect } from 'react';
  
  function useFriendStatus(friendID) {
    const [isOnline, setIsOnline] = useState(null);
  
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }
  
    useEffect(() => {
      ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
      return () => {
        ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
      };
    });
  
    return isOnline;
  }
  

Он принимает в качестве аргумента friendID и возвращает информацию о том, находится ли наш друг в онлайн.

Теперь мы можем использовать его из обоих компонентов:


Код
    
  function FriendStatus(props) {
    const isOnline = useFriendStatus(props.friend.id);
  
    if (isOnline === null) {
      return 'Загрузка...';
    }
    return isOnline ? 'Онлайн' : 'Офлайн';
  }
  


Код
    
  function FriendListItem(props) {
    const isOnline = useFriendStatus(props.friend.id);
  
    return (
      <li style={{ color: isOnline ? 'green' : 'black' }}>
        {props.friend.name}
      </li>
    );
  }
  

Состояния этих компонентов полностью независимы. Хуки - это способ повторно использовать логику с состоянием, а не само состояние. Фактически, каждый вызов хука имеет полностью изолированное состояние - так что вы даже можете использовать один и тот же пользовательский хук дважды в одном компоненте.

Пользовательские хуки - это скорее соглашение, чем функциональная возможность. Если имя функции начинается с use и вызывает другие хуки, мы говорим, что это пользовательский хук. Соглашение об использовании имен useSomething - это то, с помощью чего наш плагин linter может находить ошибки в коде, использущем хуки.

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


Детальное объяснение

Вы можете узнать больше об этих правилах в разделе Создание ваших собственных хуков.




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


Код
    
  function Example() {
    const locale = useContext(LocaleContext);
    const theme = useContext(ThemeContext);
    // ...
  }
  

А useReducer позволяет вам управлять локальным состоянием сложных компонентов с помощью редьюсера:


Код
    
  function Todos() {
    const [todos, dispatch] = useReducer(todosReducer);
    // ...
  


Детальное объяснение

Вы можете узнать больше обо всех встроенных хуках в разделе Справка по API Хуков.




Что ж, это было быстро! Если некоторые вещи не совсем понятны, или вы хотите узнать больше, тогда можете прочитать следующие разделы, начиная с Использование хука состояния.

Также вы также можете ознакомиться со справкой по API Хуков и FAQ по хукам.

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