Spec-Zone.ru › React 17

Состояние и жизненный цикл

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

Рассмотрим пример тикающих часов из одного из предыдущих разделов. В Разделе «Отображение элементов» мы узнали только один способ обновления пользовательского интерфейса. Мы вызываем ReactDOM.render() для изменения отображаемого результата:

function tick() {
  const element = (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {new Date().toLocaleTimeString()}.</h2>
    </div>
  );
  ReactDOM.render(
    element,
    document.getElementById('root')
  );
}

setInterval(tick, 1000);

Попробовать на CodePen

В этом разделе мы узнаем, как сделать компонент Clock действительно многократно используемым и инкапсулированным. Он будет настраивать свой таймер и обновлять себя каждую секунду.

Мы можем начать с инкапсуляции внешнего вида часов:

function Clock(props) {
  return (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {props.date.toLocaleTimeString()}.</h2>
    </div>
  );
}

function tick() {
  ReactDOM.render(
    <Clock date={new Date()} />,
    document.getElementById('root')
  );
}

setInterval(tick, 1000);

Попробовать на CodePen

Однако, он упускает важнейшее требование: тот факт, что компонент Clock устанавливает таймер и обновляет пользовательский интерфейс каждую секунду, должен быть деталью реализации компонента Clock.

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

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

Для реализации этого нам нужно добавить «состояние» к компоненту Clock.

Состояние похоже на свойства, но оно является частным и полностью контролируется компонентом.

Преобразование функции в класс

Вы можете преобразовать компонент функции, например Clock, в класс в пять шагов:

  1. Создайте класс ES6 с тем же именем, что и React.Component.
  2. Добавьте в него один пустой метод с именем render().
  3. Переместите тело функции в метод render().
  4. Замените props на this.props в теле метода render().
  5. Удалите оставшееся пустое объявление функции.
class Clock extends React.Component {
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.props.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

Попробовать на CodePen

Clock теперь определен как класс, а не как функция.

Метод render будет вызываться каждый раз, когда происходит обновление, но пока мы отображаем <Clock /> в тот же узел DOM, будет использоваться только один экземпляр класса Clock. Это позволяет нам использовать дополнительные возможности, такие как локальное состояние и методы жизненного цикла.

Добавление локального состояния к классу

Мы перенесем date из свойств в состояние в три шага:

  1. Замените this.props.date на this.state.date в методе render().
class Clock extends React.Component {
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}
  1. Добавьте конструктор класса, который задает начальное значение this.state:
class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

Обратите внимание, как мы передаем props в базовый конструктор:

  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

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

  1. Удалите свойство date из элемента <Clock />:
ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

Позже мы добавим код таймера обратно в сам компонент.

Результат выглядит так:

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

Попробовать на CodePen

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

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

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

Мы хотим настроить таймер всякий раз, когда компонент Clock впервые отображается в DOM. Это называется «монтированием» в React.

Мы также хотим очистить этот таймер, когда элемент DOM, созданный компонентом Clock, удаляется. Это называется «размонтированием» в React.

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

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  componentDidMount() {

  }

  componentWillUnmount() {

  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

Эти методы называются «методами жизненного цикла».

Метод componentDidMount() выполняется после того, как выходные данные компонента были отображены в DOM. Это хорошее место для настройки таймера:

  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }

Обратите внимание, как мы сохраняем идентификатор таймера непосредственно в this (this.timerID).

В то время как this.props настраивается самим React, а this.state имеет специальное значение, вы можете свободно добавлять дополнительные поля в класс вручную, если вам нужно хранить что-то, что не участвует в потоке данных (например, идентификатор таймера).

Мы размонтируем таймер в методе жизненного цикла componentWillUnmount():

  componentWillUnmount() {
    clearInterval(this.timerID);
  }

Наконец, мы реализуем метод под названием tick(), который компонент Clock будет выполнять каждую секунду.

Он будет использовать this.setState() для планирования обновлений локального состояния компонента:

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>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

Попробовать на CodePen

Теперь часы тикают каждую секунду.

Давайте быстро повторим, что происходит и в каком порядке вызываются методы:

  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. Внутри него компонент Clock планирует обновление пользовательского интерфейса, вызывая setState() с объектом, содержащим текущее время. Благодаря вызову setState(), React знает, что состояние изменилось, и снова вызывает метод render() для определения того, что должно быть на экране. На этот раз значение this.state.date в методе render() будет другим, и, следовательно, результат рендеринга будет включать обновленное время. React обновляет DOM соответственно.
  5. Если компонент Clock когда-либо удаляется из DOM, React вызывает метод жизненного цикла componentWillUnmount() для остановки таймера.

Правильное использование состояния

Есть три вещи, которые вам следует знать о setState().

Не изменяйте состояние напрямую

Например, это не приведет к повторному рендерингу компонента:

// Wrong
this.state.comment = 'Hello';

Вместо этого используйте setState():

// Correct
this.setState({comment: 'Hello'});

Единственное место, где вы можете присвоить this.state — конструктор.

Обновления состояния могут быть асинхронными

React может объединять несколько вызовов setState() в одно обновление для повышения производительности.

Поскольку this.props и this.state могут обновляться асинхронно, вы не должны полагаться на их значения для расчета следующего состояния.

Например, этот код может не обновить счетчик:

// Wrong
this.setState({
  counter: this.state.counter + this.props.increment,
});

Для исправления этого используйте вторую форму setState(), которая принимает функцию вместо объекта. Эта функция получит предыдущее состояние в качестве первого аргумента и свойства в момент применения обновления как второго аргумента:

// Correct
this.setState((state, props) => ({
  counter: state.counter + props.increment
}));

Мы использовали выше стрелочную функцию, но она также работает с обычными функциями:

// Correct
this.setState(function(state, props) {
  return {
    counter: state.counter + props.increment
  };
});

Обновления состояния объединяются

При вызове setState(), React объединяет предоставляемый вами объект в текущее состояние.

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

  constructor(props) {
    super(props);
    this.state = {
      posts: [],
      comments: []
    };
  }

Затем вы можете обновить их независимо с помощью отдельных вызовов setState():

  componentDidMount() {
    fetchPosts().then(response => {
      this.setState({
        posts: response.posts
      });
    });

    fetchComments().then(response => {
      this.setState({
        comments: response.comments
      });
    });
  }

Объединение является поверхностным, поэтому this.setState({comments}) оставляет this.state.posts неизменным, но полностью заменяет this.state.comments.

Поток данных снизу вверх

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

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

Компонент может выбрать передачу своего состояния как свойств своим дочерним компонентам:

<FormattedDate date={this.state.date} />

Компонент FormattedDate получит date в своих свойствах и не будет знать, поступило ли оно из состояния Clock, из свойств Clock, или было введено вручную:

function FormattedDate(props) {
  return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}

Попробовать на CodePen

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

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

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

function App() {
  return (
    <div>
      <Clock />
      <Clock />
      <Clock />
    </div>
  );
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
);

Попробовать на CodePen

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

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

Полезная ли эта страница?

© 2013–present Facebook Inc.
Licensed under the Creative Commons Attribution 4.0 International Public License.
https://17.reactjs.org/docs/state-and-lifecycle.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API