Перейти к содержанию

Краткий обзор хуков

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

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

Подробное объяснение

Если вы хотите понять, почему мы добавляем хуки в 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>
  )
}

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

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

Объявление нескольких переменных состояния

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

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

Синтаксис деструктуризации массивов позволяет нам по разному называть переменные состояния, которые мы объявляем при вызове useState. Так как имён этих переменных нет в API useState, React предполагает, что если вы вызываете useState много раз, вы делаете это в одинаковой последовательности при каждом рендере. Мы расскажем, почему это работает и когда это целесообразно, немного позже.

Что же такое хук?

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

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

Подробное объяснение

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

⚡️ Хук эффекта

Вам скорее всего доводилось ранее запрашивать данные, делать подписки или вручную менять DOM из React-компонента. Мы расцениваем эти операции как "побочные эффекты" (или сокращённо "эффекты"), так как они могут влиять на работу других компонентов и их нельзя выполнить во время рендера.

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

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

import React, { 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>
  )
}

Когда вы вызываете useEffect, React получает указание запустить вашу функцию с "эффектом" после того, как он отправил изменения в DOM. Поскольку эффекты объявляются внутри компонента, у них есть доступ к его пропсам и состоянию. По умолчанию, React запускает эффекты после каждого рендера, включая первый рендер. Мы рассмотрим более подробно, как это отличается от классовых методов жизненного цикла на странице использование хука эффекта.

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

import React, { 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);
  }
  // ...

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

Подробное объяснение

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

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

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

  • Хуки следует вызывать только на верхнем уровне. Не вызывайте хуки внутри циклов, условий или вложенных функций.
  • Хуки следует вызывать только из функциональных компонентов React. Не вызывайте хуки из обычных JavaScript-функций. Есть только одно исключение, откуда можно вызывать хуки — это ваши пользовательские хуки. Мы расскажем о них далее.

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

Подробное объяснение

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

💡 Создание собственных хуков

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

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

Прежде всего, давайте извлечём эту логику в пользовательский хук useFriendStatus

import React, { 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 при именовании хуков, это позволит нашему плагину для линтера найти баги в коде, который использует хуки.

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

Подробное объяснение

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

🔌 Другие хуки

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

function Example() {
  const locale = useContext(LocaleContext)
  const theme = useContext(ThemeContext)
  // ...
}

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

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

Подробное объяснение

Вы можете узнать больше обо всех встроенных хуках на странице API-справочника хуков.

Что дальше?

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

Вы также можете просмотреть API-справочник хуков и FAQ хуков.

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