Состояние и жизненный цикл
На этой странице представлено понятие состояния и жизненного цикла в компоненте 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); В этом разделе мы узнаем, как сделать компонент 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')
); Для реализации этого нам нужно добавить «состояние» к компоненту Clock.
Состояние похоже на свойства, но оно является частным и полностью контролируется компонентом.
Преобразование функции в класс
Вы можете преобразовать компонент функции, например Clock, в класс в пять шагов:
- Создайте класс ES6 с тем же именем, что и
React.Component. - Добавьте в него один пустой метод с именем
render(). - Переместите тело функции в метод
render(). - Замените
propsнаthis.propsв теле методаrender(). - Удалите оставшееся пустое объявление функции.
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {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>Hello, world!</h1>
<h2>It is {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>Hello, world!</h1>
<h2>It is {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>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('root')
); Далее мы сделаем так, чтобы компонент 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')
); Теперь часы тикают каждую секунду.
Давайте быстро повторим, что происходит и в каком порядке вызываются методы:
- Когда
<Clock />передается вReactDOM.render(), React вызывает конструктор компонентаClock. ПосколькуClockнеобходимо отображать текущее время, он инициализируетthis.stateс объектом, включающим текущее время. Позже мы обновим это состояние. - Затем React вызывает метод
Clockкомпонентаrender(). Именно так React узнает, что должно отображаться на экране. Затем React обновляет DOM, чтобы он соответствовал результату рендеринга компонентаClock. - Когда вывод
Clockвставляется в DOM, React вызывает метод жизненного циклаcomponentDidMount(). Внутри него компонентClockзапрашивает у браузера настройку таймера для вызова метода компонентаtick()один раз в секунду. - Каждую секунду браузер вызывает метод
tick()компонентаClock. Внутри него компонентClockпланирует обновление пользовательского интерфейса, вызываяsetState()с объектом, содержащим текущее время. Благодаря вызовуsetState(), React знает, что состояние изменилось, и снова вызывает методrender()для определения того, что должно быть на экране. На этот раз значениеthis.state.dateв методеrender()будет другим, и, следовательно, результат рендеринга будет включать обновленное время. React обновляет DOM соответственно. - Если компонент
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>;
} Это обычно называется «потоком данных сверху вниз» или «однонаправленным» потоком данных. Любое состояние всегда принадлежит определенному компоненту, и любые данные или пользовательский интерфейс, полученные из этого состояния, могут влиять только на компоненты «ниже» них в дереве.
Если представить дерево компонентов как водопад свойств, состояние каждого компонента подобно дополнительному источнику воды, который присоединяется к нему в произвольной точке, но также течет вниз.
Чтобы показать, что все компоненты действительно изолированы, мы можем создать компонент App , который отображает три компонента <Clock>:
function App() {
return (
<div>
<Clock />
<Clock />
<Clock />
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('root')
); Каждый компонент 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