Состояние компонента React (State)

Компоненты могут иметь состояние (state), которое является приватной частью данных компонента и может использоваться для управления динамическими данными.

Состояние применяется только к классовым компонентам, или его можно использовать в функциональных компонентах при использовании React Hooks.

React рассматривает компоненты как конечные автоматы. Через взаимодействие с пользователями реализуются разные состояния, а затем рендерится UI, чтобы поддерживать соответствие пользовательского интерфейса данным.

В React вам нужно только обновить состояние компонента, а затем повторно отрендерить пользовательский интерфейс на основе нового состояния (не манипулируйте DOM).

В следующем примере создается класс ES6, имя которого расширяет React.Component, и используется this.state в методе render() для изменения текущего времени.

Добавьте конструктор класса для инициализации состояния this.state. Классовые компоненты всегда должны вызывать базовый конструктор с props.


Управление состоянием в классовых компонентах

Создайте классовый компонент с состоянием:

Файл Counter.js

import React, { Component } from 'react';

class Counter extends Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  increment = () => {
    this.setState({ count: this.state.count + 1 });
  }

  render() {
    return (
      <div>
        <p>Счётчик: {this.state.count}</p>
        <button onClick={this.increment}>Увеличить</button>
      </div>
    );
  }
}

export default Counter;

Отрендерите компонент в src/index.js:

Пример

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import Counter from './Counter';

const root = ReactDOM.createRoot(document.getElementById("root"));
// Отрисовать компонент Counter
root.render(<Counter />);

Управление состоянием в функциональных компонентах (с использованием хуков)

Используя хуки React, вы можете использовать состояние в функциональных компонентах. Наиболее часто используемый хук — useState.

Создайте функциональный компонент с состоянием:

Файл Counter.js

import React, { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Количество: {count}</p>
      <button onClick={() => setCount(count + 1)}>Увеличить</button>
    </div>
  );
}

export default Counter;

Отрендерите компонент в src/index.js:

Пример

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import Counter from './Counter';

const root = ReactDOM.createRoot(document.getElementById("root"));
// Отрисовать компонент Counter
root.render(<Counter />);

Пример

Создайте пример Clock, чтобы понять состояние компонента:

Пример React

class Clock extends React.Component { constructor(props) { super(props); this.state = {date: new Date()}; } render() { return ( <div> <h1>Привет, мир!</h1> <h2>Сейчас{this.state.date.toLocaleTimeString()}.</h2> </div> ); } } const root = ReactDOM.createRoot(document.getElementById("root")); root.render( <Clock /> );

Попробуйте »

Далее мы заставим Clock настроить собственный таймер и обновляться каждую секунду.

Добавление методов жизненного цикла в класс

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

Когда компонент Clock впервые загружается в DOM, мы хотим настроить таймер. В React это называетсямонтирование。

Аналогично, когда DOM, сгенерированный Clock, удаляется, мы также хотим очистить таймер. В React это называетсяразмонтирование。

Мы можем объявить специальные методы в классе компонента, чтобы выполнить некоторый код, когда компонент монтируется или размонтируется:

Пример React

class Clock extends React.Component { constructor(props) { super(props); this.state = {date: new Date()}; } componentDidMount() { this.timerID = setInterval( () => this.tick(), 1000 ); } componentWillUnmount() { clearInterval(this.timerID); } tick() { this.setState({ date: new Date() }); } render() { return ( <div> <h1>Привет, мир!</h1> <h2>Сейчас{this.state.date.toLocaleTimeString()}.</h2> </div> ); } } const root = ReactDOM.createRoot(document.getElementById("root")); root.render( <Clock /> );

Попробуйте »

Анализ примера:

componentDidMount()иcomponentWillUnmount()Эти методы называются хуками жизненного цикла.

После того как вывод компонента отрендерен в DOM, выполняетсяcomponentDidMount()хук, и мы можем установить таймер в этом хуке.

this.timerID — это ID таймера, и мы можемcomponentWillUnmount()очистить таймер в этом хуке.

Порядок выполнения кода:

  1. Когда<Clock />передаётся вReactDOM.render(), React вызываетClockконструктор компонента. ПосколькуClockдолжен отображать текущее время, он инициализируется объектом, содержащим текущее времяthis.state. Мы обновим это состояние позже.

  2. Затем React вызываетClockу компонентаrender()метод. Так React узнаёт, что должно отображаться на экране, а затем React обновляет DOM, чтобы привести его в соответствиеClockрезультат рендеринга

  3. КогдаClockКогда вывод вставляется в DOM, React вызываетcomponentDidMount()хук жизненного цикла. В нём,Clockкомпонент просит браузер установить таймер, срабатывающий раз в секундуtick()。

  4. Браузер вызываетtick()метод каждую секунду. В нём,Clockкомпонент вызываетsetState()с объектом, содержащим текущее время, чтобы запланировать обновление UI. ВызываяsetState(), React знает, что состояние изменилось, и вызываетrender()снова, чтобы определить, что должно отображаться на экране. На этот раз,render()в методе,this.state.dateбудет другим, поэтому результат рендеринга будет содержать обновлённое время, и DOM обновляется соответствующим образом.

  5. Как толькоClockкомпонент удаляется из DOM, React вызываетcomponentWillUnmount()эту функцию хука, и таймер будет очищен.

Данные передаются сверху вниз

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

Вот почему состояние часто называют локальным или инкапсулированным. Никакие другие компоненты не могут получить к нему доступ, кроме компонента, который владеет им и устанавливает его.

В следующем примере компонент FormattedDate получает значение date в своих props и не знает, приходит ли оно из состояния Clock, из props Clock или из ручного ввода:

Пример React

function FormattedDate(props) { return <h2>Это{props.date.toLocaleTimeString()}.</h2>; } class Clock extends React.Component { constructor(props) { super(props); this.state = {date: new Date()}; } componentDidMount() { this.timerID = setInterval( () => this.tick(), 1000 ); } componentWillUnmount() { clearInterval(this.timerID); } tick() { this.setState({ date: new Date() }); } render() { return ( <div> <h1>Привет, мир!</h1> <FormattedDate date={this.state.date} /> </div> ); } } const root = ReactDOM.createRoot(document.getElementById("root")); root.render( <Часы /> );

Попробуйте »

Это часто называют нисходящим или односторонним потоком данных. Любое состояние всегда принадлежит какому-то конкретному компоненту, и любые данные или UI, производные от этого состояния, могут влиять только на компоненты ниже него в дереве.

Если представить дерево компонентов как водопад props, то состояние каждого компонента — как дополнительный источник воды, который присоединяется в произвольной точке, но также течёт вниз.

Чтобы показать, что все компоненты действительно изолированы, мы можем создать компонент App, который рендерит три компонента Clock:

Пример React

function FormattedDate(props) { return <h2>Это{props.date.toLocaleTimeString()}.</h2>; } class Clock extends React.Component { constructor(props) { super(props); this.state = {date: new Date()}; } componentDidMount() { this.timerID = setInterval( () => this.tick(), 1000 ); } componentWillUnmount() { clearInterval(this.timerID); } tick() { this.setState({ date: new Date() }); } render() { return ( <div> <h1>Привет, мир!</h1> <FormattedDate date={this.state.date} /> </div> ); } } function App() { return ( <div> <Clock /> <Clock /> <Clock /> </div> ); } const root = ReactDOM.createRoot(document.getElementById("root")); root.render(<App />);

Попробуйте »

В приведённом выше примере каждый компонент Clock настраивает свой собственный таймер и обновляется независимо.

В приложениях React то, является ли компонент с состоянием или без состояния, считается деталью реализации компонента, которая может со временем меняться.

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

Другие расширения