При правильном использовании React большая часть компонентов состоит из метода render
и обработчиков событий:
class ArticleItem extends React.Component {
handleClick = (e) => {
e.preventDefault();
const { onClick } = this.props;
onClick();
}
render() {
const { name, description, link } = this.props;
return (
<div>
<a href={link} onClick={this.handleClick}>{name}</a><br />
<div>{description}</div>
</div>
);
}
}
// Пример использования ArticleItem:
// <ArticleItem
// name="name"
// description="This is description"
// link="#"
// onClick={someFunction}
// />
Но не все задачи решаются так просто. Представьте себе компонент <Clock />
, имитирующий цифровые часы в формате чч:мм:сс. Заготовка:
class Clock extends React.Component {
render() {
const currentTime = new Date();
return (
<div>{currentTime.toLocaleTimeString()}</div>
);
}
}
Этот компонент отображает текущее время. Теперь нужно придумать, как его обновлять. Часы, в отличие от обычных компонентов, не ожидают действий от пользователя. Они обновляются каждую секунду самостоятельно. Возникает цепочка: происходит событие => меняется текущее время => React вызывает render
и меняет DOM. Итак, состояние инициализируется текущим временем:
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = { date: new Date() };
}
render() {
const { date } = this.state;
return (
<div>{date.toLocaleTimeString()}</div>
);
}
}
Компонент по-прежнему показывает текущее время на момент отрисовки/перерисовки компонента, но теперь он готов к изменению. Время относится к периодическим событиям, для которых используются таймеры. Для <Clock />
подойдет setInterval
. Таймер должен быть установлен сразу после отрисовки часов и должен быть очищен при удалении компонента из дерева элементов.
setInterval(() => this.setState({ date: new Date() }), 1000);
Где запускать таймер? render
вызывается на каждое изменение состояния, а значит он не подходит. Ведь тогда <Clock />
будет запускать новый таймер каждую секунду. Конструктор кажется более подходящим местом, но здесь ожидает сюрприз. Вызов конструктора и отрисовка часов в DOM-дереве, в общем случае — два независимых события. Посмотрите на код:
// Вызывается конструктор
const clock = <Clock />;
// Что-то долго делаем еще
// Отрисовываем
const root = createRoot(document.getElementById('root'));
root.render(clock);
Эти часы еще не находятся в DOM-дереве, но уже вовсю работают и обновляются. Стоит ли об этом беспокоиться? Да, такое поведение крайне неожиданно, оно мешает тестированию и расходует процессорное время. Кроме того, конструктор никак не помогает с удалением таймера.
Каждый компонент React проходит несколько стадий в процессе своей жизни: он создается, затем добавляется в DOM, получает пропсы, и, наконец, удаляется из дерева. Этот процесс называют жизненным циклом компонента (Component Lifecycle). React предоставляет набор методов, которые позволяют встроиться в этот процесс. Например, запуск часов логичнее всего сделать сразу после их отрисовки. В этом поможет метод componentDidMount
. Он вызывается сразу после отрисовки компонента. Происходит это ровно один раз.
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = { date: new Date() };
}
componentDidMount() {
// Сохраняется идентификатор таймера
this.timerId = setInterval(() => this.setState({ date: new Date() }), 1000);
}
render() {
const { date } = this.state;
return (
<div>{date.toLocaleTimeString()}</div>
);
}
}
Обратите внимание на то, как сохраняется идентификатор таймера внутри объекта. Он не участвует в представлении, поэтому нет необходимости сохранять его в состоянии.
Теперь нужно выполнить очистку таймера. Для этого подойдет метод componentWillUnmount
, который выполняется прямо перед удалением компонента из DOM.
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = { date: new Date() };
}
componentDidMount() {
this.timerId = setInterval(() => this.setState({ date: new Date() }), 1000);
}
componentWillUnmount() {
clearInterval(this.timerId);
}
render() {
const { date } = this.state;
return (
<div>{date.toLocaleTimeString()}</div>
);
}
}
Часы приобрели законченный вид.
Итак, вы узнали два метода, позволяющих встраиваться в жизненный цикл компонента, но их значительно больше. Они делятся на три независимые группы:
Монтирование (Mounting)
Эти методы вызываются по порядку во время создания объекта и вставки его в DOM.
constructor()
static getDerivedStateFromProps()
render()
componentDidMount()
Обновление (Updating)
Обновление может происходить при изменении свойств или состояния. Эти методы вызываются по порядку во время перерисовки:
static getDerivedStateFromProps()
shouldComponentUpdate()
render()
getSnapshotBeforeUpdate()
componentDidUpdate()
Удаление или демонтирование (Unmounting)
В эту группу входит один метод. Он вызывается во время удаления компонента из DOM.
componentWillUnmount()
Такое количество методов объясняется сложностью реальной разработки. Но на практике лишь некоторые используются регулярно. К таким методам относится componentDidMount
. С его помощью устанавливают таймеры, выполняют AJAX-запросы, меняют DOM в обход React. Последнее бывает нужно при интеграции со сторонними библиотеками.
Дополнительные материалы
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
- Статья «Как учиться и справляться с негативными мыслями»
- Статья «Ловушки обучения»
- Статья «Сложные простые задачи по программированию»
- Вебинар «Как самостоятельно учиться»
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.