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

Хуки — нововведение в 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, похожего на методы жизненного цикла в классах. С его помощью компоненты могут выполнять побочные эффекты.