До сих пор мы только рассмотрили один способ обновления пользовательского интерфейса.
Мы вызывали 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».
Состояние похоже на props, но оно является частным и полностью контролируется компонентом.
Мы упоминали ранее, что компоненты, определенные как классы, имеют некоторые дополнительные функции. Локальное состояние – это именно та функция которая доступна только для классов.
Преобразование функции в класс
Вы можете преобразовать функциональный компонент, например Clock, в класс за пять шагов:
- Создайте ES6 класс с тем же именем. Класс будет расширять React.Component.
- Добавьте к нему один пустой метод под названием render().
- Переместите тело функции в метод render().
- Замените prop с на 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 теперь определяются как класс, а не функция.
Это позволяет нам использовать дополнительные функции, такие как локальное состояние и перехват жизненного цикла.
Добавляем локальное состояние в класс
Мы переместим date из props в три этапа:
- Замените 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 в родительский класс. Конструктор класса компонента должен всегда вызывать базовый конструктор и передавать ему props.
constructor(props) {
super(props);
this.state = {date: new Date()};
}- Удалите
dateprop из елемента<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 в первый раз. Это называется «mount» в React.
Мы также хотим очистить этот таймер всякий раз, когда DOM, созданный Clock, удаляется. Это называется «unmount» в React.
Мы можем объявить специальные методы для класса компонентов для запуска некоторого кода, когда компонент mount и unmount:
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 настраивается самим React, this.state имеет особое значение, вы можете добавлять дополнительные поля в класс вручную, если вам нужно сохранить что-то, что не используется для визуального вывода.
Если вы не используете что-то в render(), оно не должно находиться в 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 вызывает метод
render()компонентаClock. Так React узнает, что должно отображаться на экране. Затем React обновляет DOM, чтобы соответствовать результату рендерингаClock. - Когда
Clockвставлен в DOM, React вызывает хук жизненного циклаcomponentDidMount(). Внутри компонентаClockзапрашивает у браузера настройку таймера для вызова методаtick()компонента один раз в секунду - Каждую секунду браузер вызывает метод tick(). Компонент Clock планирует обновление UI, вызвав setState() с объектом, содержащим текущее время. Благодаря вызову setState() React знает, что состояние изменилось, и снова вызывает метод render(), чтобы вывести то что должно быть на экране. На этот раз this.state.date в методе render() будет другим, и поэтому рендеринг будет включать в себя обновленное время. React обновляет DOM.
- Если компонент «
Clock» удаляется из DOM, React вызывает hookcomponentWillUnmount(), чтобы таймер был остановлен.
Используем состояние правильно
Есть три вещи, которые вы должны знать о 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(), которая принимает функцию, а не объект. Эта функция получит предыдущее состояние в качестве первого аргумента и props во время обновления в качестве второго аргумента:
// Correct
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));Мы использовали arrow function выше, но также можно писать обычные функции:
// 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.
Потоки данных
Ни родительский, ни дочерний компоненты не могут знать, является ли какой-либо компонент активным или нет, и им не важно, определено ли оно как функция или класс.
Вот почему состояние часто называют локальным или инкапсулированным. Состояние недоступно для любого компонента, кроме того, который владеет и устанавливает его.
Компонент может передать свое состояние вниз в качестве props его дочерних компонентов:
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>Это также работает для пользовательских компонентов:
<FormattedDate date={this.state.date} />Компонент FormattedDate получит дату в своих props и не будет знать, пришла ли она из состояния Clock, из props Clock или был набран вручную:
function FormattedDate(props) {
return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}Это обычно называют потоком данных «сверху вниз» или «однонаправленным». Любое состояние всегда принадлежит определенному компоненту, и любые данные или пользовательский интерфейс, полученные из этого состояния, могут влиять только на компоненты «ниже» их в дереве.
Если вы представляете дерево компонентов как водопад props, состояние каждого компонента похоже на дополнительный источник воды, который соединяет его в произвольной точке, но также течет вниз.
Чтобы показать, что все компоненты действительно изолированы, мы можем создать компонент приложения, который отображает три <Clock> :
function App() {
return (
<div>
<Clock />
<Clock />
<Clock />
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('root')
);Каждый Clock устанавливает свой собственный таймер и обновляется независимо.
В React, независимо от того, является ли компонент устаревшим или неактивным, рассматривается как деталь реализации компонента, которая может меняться со временем. Вы можете использовать компоненты без состояния в компонентах stateful, и наоборот.


Спасибо за перевод, и у вас очепятка “moutn”
Спасибо, исправил 🙂
prot –> prop опечатка
Спасибо 🙂