3.12.3 Хук состояния


Хуки доступны в версии React 16.8. Они позволяют вам использовать состояние и другие функции 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>
    );
  }
  

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



3.12.3.1 Эквивалентный пример класса


Если ранее вы использовали классы в 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, пока мы делаем первые шаги с помощью хуков.



3.12.3.2 Хуки и компоненты-функции


Напоминаем, что компоненты-функции в React выглядят так:


Код
    
  const Example = (props) => {
    // Вы можете использовать хуки здесь!
    return <div />;
  }
  

или так:


Код
    
  function Example (props) {
    // Вы можете использовать хуки здесь!
    return <div />;
  }
  

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



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



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


Наш новый пример начинается с импорта хука useState из React:


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

  function Example() {
    // ...
  }
  

Что такое хук? Хук - это специальная функция, которая позволяет «прицепиться» к возможностям React. Например, useState - это хук, который позволяет добавлять состояние React к компонентам-функциям. С другими хуками мы познакомимся позже.

Когда можно использовать хук? Если вы пишете компонент-функцию и в какой-то понимаете, что вам нужно добавить в него какое-то состояние, то ранее вам приходилось преобразовывать его в класс. Теперь вы можете использовать хук внутри существующего компонента-функции. Давайте сделаем это прямо сейчас!


Внимание!

Есть некоторые специальные правила относительно того, где вы можете и не можете использовать хуки в компоненте. Мы узнаем о них в разделе Правила использования хуков.



3.12.3.4 Объявление переменной состояния


В классе мы инициализируем состояние счетчика значением 0, устанавливая this.state равным {count: 0} в конструкторе:


Код
    
  class Example extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        count: 0
      };
    }
  

В компоненте-функции у нас такой возможности нет, поэтому мы не можем назначить или прочитать this.state. Вместо этого мы вызываем хук useState прямо внутри нашего компонента:


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

  function Example() {
    // Объявляем новую переменную состояния, кокторую назовём "count"
    const [count, setCount] = useState(0);
  

Что делает вызов useState? Он объявляет «переменную состояния». Это способ «сохранять» некоторые значения между вызовами функций. Наша переменная называется count, но мы можем назвать ее как угодно, например, banana. useState - это новый способ использовать точно такие же возможности, которые this.state предоставляет в классе. Обычно переменные теряются при выходе из функции, но переменные состояния сохраняются React-ом.

Что мы передаем useState в качестве аргумента? Единственный аргумент для хука useState() - это начальное состояние. В отличие от классов, состояние не обязательно должно быть объектом. Мы можем сохранять число или строку, если это все, что нам нужно. В нашем примере мы хотим хранить просто число, показывающее сколько раз пользователь кликал, поэтому передаем 0 в качестве начального состояния для нашей переменной. (Если бы мы хотели сохранить два разных значения в состоянии, мы бы вызвали useState() дважды.)

Что возвращает useState? Он возвращает два значения: текущее состояние и функцию, которая его обновляет. Вот почему мы пишем const [count, setCount] = useState(). Это похоже на this.state.count и this.setState в классе, за исключением того, что вы получаете их в паре. Если вы не знакомы с синтаксисом, который мы использовали, мы вернемся к нему внизу этого раздела.

Теперь, когда мы знаем, что делает хук useState, наш пример должен иметь больше смысла:


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

  function Example() {
    // Объявляем новую переменную состояния, кокторую назовём "count"
    const [count, setCount] = useState(0);
  

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




Внимание!

Вы можете задаться вопросом: почему useState не называется createState?

Слово «создать» будет не совсем точным, поскольку состояние создается только при первой отрисовке нашего компонента. Во время следующих отрисовок useState возвращает нам текущее состояние. Иначе это не является "состоянием" вообще! Существует также причина, по которой имена хуков всегда начинаются с use. Мы узнаем почему в разделе Правила использования хуков.



3.12.3.5 Чтение состояния


Когда мы хотим отобразить текущий счетчик в классе, мы считываем this.state.count:


Код
    
  <p>Вы кликнули {this.state.count} раз</p>
  

В функции мы можем использовать count напрямую:


Код
    
  <p>Вы кликнули {count} раз</p>
  



3.12.3.6 Обновление состояния


В классе нам нужно вызвать this.setState(), чтобы обновить состояние счетчика:


Код
    
  <button onClick={() => this.setState({ count: this.state.count + 1 })}>
    Кликни меня
  </button>
  

В функции у нас теперь есть переменные setCount и count, поэтому такой вызов не нужен:


Код
    
 <button onClick={() => setCount(count + 1)}>
    Кликни меня
 </button>
  



3.12.3.7 Резюме


Теперь давайте резюмируем то, что мы узнали, построчно и проверим наше понимание.


Код
    
  1:  import { 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 повторно выполнит отрисовку компонента Example, передав ему новое значение count.

Поначалу это может показаться слишком сложным. Но не торопитесь! Если вы запутались в нашем объяснении, снова посмотрите на приведенный выше код и попробуйте прочитать его сверху вниз. Мы обещаем, что как только вы попытаетесь «забыть», как работает состояние в классах, и посмотрите на этот код свежим взглядом, его смысл станет полностью понятен.


3.12.3.7.1 Подсказка: что означают квадратные скобки?


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


Код
    
  const [count, setCount] = useState(0);
  

Имена слева не являются частью React API. Вы можете назначить свои собственные имена переменным состояния:


Код
    
  const [fruit, setFruit] = useState('банан');
  

Данный синтаксис JavaScript называется «деструктуризация массива». Он означает, что мы создаем две новые переменные fruit и setFruit, где во fruit устанавливается первое значение, возвращаемое useState, а в setFruit второе. Это эквивалентно следующему коду:


Код
    
  var fruitStateVariable = useState('банан'); // возвращает пару
  var fruit = fruitStateVariable[0]; // первый элемент в паре
  var setFruit = fruitStateVariable[1]; // второй элемент в паре
  

Когда мы объявляем переменную состояния с помощью useState, он возвращает пару - массив с двумя элементами. Первый элемент - это текущее значение, а второй - функция, которая позволяет нам его обновлять. Использование [0] и [1] для доступа к ним немного сбивает с толку, поскольку они имеют конкретное смысловое значение. Вот почему вместо этого мы используем деструктуризацию массива.


Внимание!

Вам может быть любопытно, как React узнает, какому компоненту соответствует useState, ведь мы не передаем React никакой информации вроде this. Мы ответим на этот и многие другие вопросы в разделе FAQ по хукам.


3.12.3.7.1 Подсказка: использование множества переменных состояния


Объявление переменных состояния как пары [нечто, setНечто] также удобно потому, что позволяет нам давать им разные имена, если мы хотим использовать несколько переменных:


Код
    
  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.



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


В этом разделе мы узнали об одном из хуков, предоставляемых React, который называется useState. Мы иногда будем называть его «хук состояния». Он позволяет нам добавлять локальное состояние к компонентам-функциям React, что нам удалось сделать впервые!

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

Теперь давайте перейдем к изучению следующего хука: useEffect. Он позволяет выполнять побочные эффекты в компонентах и аналогичен методам жизненного цикла в классах.