Перевод официальной документации библиотеки React.js на русском языке.
Оглавление:
1 — Часть первая — Установка
2 — Часть вторая — Внедрение JSX
3 — Часть третья — Отрисовка элементов
4 — Часть четвертая — Компоненты и свойства
5 — Часть пятая — Состояние и жизненный цикл
6 — Часть шестая — Обработка событий
7 — Часть седьмая — Условный рендеринг
8 — Часть восьмая (скоро)
Состояние и жизненный цикл
На данный момент, мы знаем только один способ как обновить пользовательский интерфейс.
Мы отправляем сигнал в 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.
Однако, из вида упускается главное требование: когда часы устанавливают таймер и обновляют UI, каждая секунда должна быть отдельной деталью реализации часов.
В идеале, мы хотим прописать код часов один раз и получить такие часы, которые будут самообновляться:
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>
);
}
}
Попробуйте повторить этот пример в CodePen.
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. Добавьте class constructor, которому присваиваются инициалы 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')
);
Попробуйте повторить этот пример в CodePen. Далее, мы сделаем так, чтобы Clock установил свой таймер и самообновлялся каждую секунду.
Добавление алгоритма жизненного цикла в класс
В приложениях с большим количеством компонентов, очень важно высвободить ресурсы, используемые компонентами, перед тем, как они будут уничтожены.
Мы хотим установить таймер, когда Clock выводятся в DOM впервые.
Мы также хотим, чтобы таймер очищался каждый раз, когда DOM, произведенный от Clock, удаляется.
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
);
}
Обратите внимание, что мы сохраняем ID таймера в this.
В то время как React устанавливает this.props, а this.state имеет особое значение, вы можете вручную добавлять дополнительные поля к классу, если вы хотите хранить то, что не используется для визуального вывода.
Если вы ничего не используете в render(), то не нужно ничего добавлять.
Мы разберем таймер в привязке жизненного цикла componentWillUnmount():
componentWillUnmount() {
clearInterval(this.timerID);
}
Наконец, мы внедрим алгоритм tick(), который выполняется каждую секунду. Он будет использовать 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. Теперь часы тикают каждую секунду.
Правильное использование состояния
Есть три вещи, касающиеся setState(), о которых вы должны знать.
Не изменяйте состояние напрямую
Например, так перерисовать компонент не получится:
// Wrong
this.state.comment = 'Hello';
Вместо этого, используйте setState():
// Correct
this.setState({comment: 'Hello'});
Обновления состояния могут быть асинхронными.
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
}));
Выше мы использовали стрелочную функцию (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.
Поток информации
Ни основной, ни дочерний компонент не может определять является ли какой-либо компонент отслеживаемым или нет. Они также не определяют, какие компоненты относятся к классам, а какие к функциям.
Именно поэтому состояние часто называется локальным или инкапсулированным. Оно недоступно ни одному компоненту, кроме того, который устанавливает состояние и управляет им.
Компонент способен передавать свое состояние как свойство для своих дочерних компонентов.
<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>;
}
Попробуйте повторить этот пример в CodePen.
Обычно, такой поток информации называется нисходящим или однонаправленный. Любое состояние, которое управляется определенным компонентом и любая информация или UI, выработанная из этого состояния, может повлиять только на «нижние» компоненты дерева.
Если представить дерево компонентов как водопад свойств, каждое состояние компонента является дополнительным источником воды, который присоединяется к водопаду в любом месте и также стекает вниз.
Чтобы показать, что все элементы действительно изолированы, мы можем создать компонент приложения (App), который отражает три компонента <Clock>:
function App() {
return (
<div>
<Clock />
<Clock />
<Clock />
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
Попробуйте повторить этот пример в CodePen.
Каждый Clock устанавливает свои таймер и независимо обновляется.
В приложениях React, независимо от того, является ли компонент отслеживаемым или нет, он является деталью реализации компонента, который со временем может изменяться. Вы можете использовать неотслеживаемые компоненты внутри отслеживаемых и наоборот.
Поделиться с друзьями
Комментарии (2)
icepro
04.12.2016 14:51Ни основной, ни дочерний компонент не может определять является ли какой-либо компонент отслеживаемым или нет. Они также не определяют, какие компоненты относятся к классам, а какие к функциям.
Вот тут немного не понятны определения отслеживаемости и отношения к классам/функциям. Можете пояснить что это и какую роль играет для разработчика?
YozhEzhi
Продолжайте…