Unetway

ReactJS - Состояние (state) и жизненный цикл

Рассмотрим пример работы часов. Мы привязываем 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);

В этом пример будет описано, как сделать 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);

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

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

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

Чтобы реализовать это, нам нужно добавить «состояние» (state) к 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>
    );
  }
}

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>
    );
  }
}

2) Добавьте конструктор класса, который назначает начальную 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.

3) Снимите 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')
);

Затем мы сделаем Clock настройку собственного таймера и обновим каждую секунду.

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

В приложениях со многими компонентами очень важно высвобождать ресурсы, используемые компонентами при их уничтожении. Мы хотим настроить таймер всякий раз, когда Clock отображается в DOM в первый раз. Это называется «установка» (mounting) в React. Мы также хотим очистить этот таймер всякий раз, когда DOM, созданный Clock удаляется. Это называется «размонтирование» (unmounting) в 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.props настроен самим Реактом и 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')
);

Правильное использование setState()

Есть три вещи, которые вы должны знать о 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((prevState, props) => ({
  counter: prevState.counter + props.increment
}));

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

// Correct
this.setState(function(prevState, props) {
  return {
    counter: prevState.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.

Потоки данных

Ни родительский, ни дочерний компоненты не могут знать, является ли какой-либо компонент состоятельным или неактивным, и им не важно, определено ли оно как функция или класс. Вот почему состояние часто называют локальным или инкапсулированным. Он недоступен для любого компонента, кроме того, который владеет и устанавливает его. Компонент может выбрать передачу своего состояния вниз в качестве реквизита его дочерних компонентов:

<h2>It is {this.state.date.toLocaleTimeString()}.</h2>

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

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

FormattedDate компонент будет получать date в его реквизите и не знал бы , пришел ли он из Clock от реквизита, или был набран вручную:

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

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

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

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

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

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

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

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