Состояние и жизненный цикл
На этой странице представлены понятия «состояние» (state) и «жизненный цикл» (lifecycle) React-компонентов. Подробный справочник API компонентов находится по этой ссылке.
В качестве примера рассмотрим идущие часы из предыдущего раздела. В главе Рендеринг элементов мы научились обновлять UI только одним способом — вызовом ReactDOM.render():
function tick() {
  const element = (
    <div>
      <h1>Привет, мир!</h1>
      <h2>Сейчас {new Date().toLocaleTimeString()}.</h2>
    </div>
  );
  ReactDOM.render(    element,    document.getElementById('root')  );}
setInterval(tick, 1000);В этой главе мы узнаем, как инкапсулировать и обеспечить многократное использование компонента Clock. Компонент самостоятельно установит свой собственный таймер и будет обновляться раз в секунду.
Для начала, извлечём компонент, показывающий время:
function Clock(props) {
  return (
    <div>      <h1>Привет, мир!</h1>      <h2>Сейчас {props.date.toLocaleTimeString()}.</h2>    </div>  );
}
function tick() {
  ReactDOM.render(
    <Clock date={new Date()} />,    document.getElementById('root')
  );
}
setInterval(tick, 1000);Проблема в том, что компонент Clock не обновляет себя каждую секунду автоматически. Хотелось бы спрятать логику, управляющую таймером, внутри самого компонента Clock.
В идеале мы бы хотели реализовать Clock таким образом, чтобы компонент сам себя обновлял:
ReactDOM.render(
  <Clock />,  document.getElementById('root')
);Для этого добавим так называемое «состояние» (state) в компонент Clock.
«Состояние» очень похоже на уже знакомые нам пропсы, отличие в том, что состояние контролируется и доступно только конкретному компоненту.
Преобразование функционального компонента в классовый
Давайте преобразуем функциональный компонент Clock в классовый компонент за 5 шагов:
- Создаём ES6-класс с таким же именем, указываем 
React.Componentв качестве родительского класса - Добавим в класс пустой метод 
render() - Перенесём тело функции в метод 
render() - Заменим 
propsнаthis.propsв телеrender() - Удалим оставшееся пустое объявление функции
 
class Clock extends React.Component {
  render() {
    return (
      <div>
        <h1>Привет, мир!</h1>
        <h2>Сейчас {this.props.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}Теперь Clock определён как класс, а не функция.
Метод render будет вызываться каждый раз, когда происходит обновление. Так как мы рендерим <Clock /> в один и тот же DOM-контейнер, мы используем единственный экземпляр класса Clock — поэтому мы можем задействовать внутреннее состояние и методы жизненного цикла.
Добавим внутреннее состояние в класс
Переместим date из пропсов в состояние в три этапа:
- Заменим 
this.props.dateнаthis.state.dateв методеrender(): 
class Clock extends React.Component {
  render() {
    return (
      <div>
        <h1>Привет, мир!</h1>
        <h2>Сейчас {this.state.date.toLocaleTimeString()}.</h2>      </div>
    );
  }
}- Добавим конструктор класса, в котором укажем начальное состояние в переменной 
this.state: 
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>
    );
  }
}Обратите внимание, что мы передаём props базовому (родительскому) конструктору:
  constructor(props) {
    super(props);    this.state = {date: new Date()};
  }Классовые компоненты всегда должны вызывать базовый конструктор с аргументом props.
- Удалим проп 
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>Привет, мир!</h1>
        <h2>Сейчас {this.state.date.toLocaleTimeString()}.</h2>      </div>
    );
  }
}
ReactDOM.render(
  <Clock />,  document.getElementById('root')
);Теперь осталось только установить собственный таймер внутри Clock и обновлять компонент каждую секунду.
Добавим методы жизненного цикла в класс
В приложениях со множеством компонентов очень важно освобождать используемые системные ресурсы, когда компоненты удаляются.
Первоначальный рендеринг компонента в DOM называется «монтирование» (mounting). Нам нужно устанавливать таймер всякий раз, когда это происходит.
Каждый раз когда DOM-узел, созданный компонентом, удаляется, происходит «размонтирование» (unmounting). Чтобы избежать утечки ресурсов, мы будем сбрасывать таймер при каждом «размонтировании».
Объявим специальные методы, которые компонент будет вызывать при монтировании и размонтировании:
class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }
  componentDidMount() {  }
  componentWillUnmount() {  }
  render() {
    return (
      <div>
        <h1>Привет, мир!</h1>
        <h2>Сейчас {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}Эти методы называются «методами жизненного цикла» (lifecycle methods).
Метод componentDidMount() запускается после того, как компонент отрендерился в DOM — здесь мы и установим таймер:
  componentDidMount() {
    this.timerID = setInterval(      () => this.tick(),      1000    );  }Обратите внимание, что мы сохраняем ID таймера в this (this.timerID).
Поля this.props и this.state в классах — особенные, и их устанавливает сам React. Вы можете вручную добавить новые поля, если компоненту нужно хранить дополнительную информацию (например, ID таймера).
Теперь нам осталось сбросить таймер в методе жизненного цикла componentWillUnmount():
  componentWillUnmount() {
    clearInterval(this.timerID);  }Наконец, реализуем метод tick(). Он запускается таймером каждую секунду и вызывает this.setState().
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>Привет, мир!</h1>
        <h2>Сейчас {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}
ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);Теперь часы обновляются каждую секунду.
Давайте рассмотрим наше решение и разберём порядок, в котором вызываются методы:
- Когда мы передаём 
<Clock />вReactDOM.render(), React вызывает конструктор компонента.Clockдолжен отображать текущее время, поэтому мы задаём начальное состояниеthis.stateобъектом с текущим временем. - React вызывает метод 
render()компонентаClock. Таким образом React узнаёт, что отобразить на экране. Далее React обновляет DOM так, чтобы он соответствовал выводу рендераClock. - Как только вывод рендера 
Clockвставлен в DOM, React вызывает метод жизненного циклаcomponentDidMount(). Внутри него компонентClockуказывает браузеру установить таймер, который будет вызыватьtick()раз в секунду. - Таймер вызывает 
tick()ежесекундно. Внутриtick()мы просим React обновить состояние компонента, вызываяsetState()с текущим временем. React реагирует на изменение состояния и снова запускаетrender(). На этот разthis.state.dateв методеrender()содержит новое значение, поэтому React заменит DOM. Таким образом компонентClockкаждую секунду обновляет UI. - Если компонент 
Clockкогда-либо удалится из DOM, React вызовет метод жизненного циклаcomponentWillUnmount()и сбросит таймер. 
Как правильно использовать состояние
Важно знать три детали о правильном применении setState().
Не изменяйте состояние напрямую
В следующем примере повторного рендера не происходит:
// Неправильно
this.state.comment = 'Привет';Вместо этого используйте setState():
// Правильно
this.setState({comment: 'Привет'});Конструктор — это единственное место, где вы можете присвоить значение this.state напрямую.
Обновления состояния могут быть асинхронными
React может сгруппировать несколько вызовов setState() в одно обновление для улучшения производительности.
Поскольку this.props и this.state могут обновляться асинхронно, вы не должны полагаться на их текущее значение для вычисления следующего состояния.
Например, следующий код может не обновить счётчик:
// Неправильно
this.setState({
  counter: this.state.counter + this.props.increment,
});Правильно будет использовать второй вариант вызова setState(), который принимает функцию, а не объект. Эта функция получит предыдущее состояние в качестве первого аргумента и значения пропсов непосредственно во время обновления в качестве второго аргумента:
// Правильно
this.setState((state, props) => ({
  counter: state.counter + props.increment
}));В данном примере мы использовали стрелочную функцию, но можно использовать и обычные функции:
// Правильно
this.setState(function(state, props) {
  return {
    counter: state.counter + props.increment
  };
});Обновления состояния объединяются
Когда мы вызываем setState(), React объединит аргумент (новое состояние) c текущим состоянием.
Например, состояние может состоять из нескольких независимых полей:
  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 или просто JavaScript-выражения:
function FormattedDate(props) {
  return <h2>Сейчас {props.date.toLocaleTimeString()}.</h2>;
}Этот процесс называется «нисходящим» («top-down») или «однонаправленным» («unidirectional») потоком данных. Состояние всегда принадлежит определённому компоненту, а любые производные этого состояния могут влиять только на компоненты, находящиеся «ниже» в дереве компонентов.
Если представить иерархию компонентов как водопад пропсов, то состояние каждого компонента похоже на дополнительный источник, который сливается с водопадом в произвольной точке, но также течёт вниз.
Чтобы показать, что все компоненты действительно изолированы, создадим компонент App, который рендерит три компонента <Clock>:
function App() {
  return (
    <div>
      <Clock />      <Clock />      <Clock />    </div>
  );
}
ReactDOM.render(
  <App />,
  document.getElementById('root')
);У каждого компонента Clock есть собственное состояние таймера, которое обновляется независимо от других компонентов.
В React-приложениях, имеет ли компонент состояние или нет — это внутренняя деталь реализации компонента, которая может меняться со временем. Можно использовать компоненты без состояния в компонентах с состоянием, и наоборот.