Стан та життєвий цикл
На цій сторінці представлено поняття стану та життєвого циклу у React-компоненті. Ви можете знайти детальний API-довідник по компонентах тут.
Розглянемо приклад відліку годинника з одного з попередніх розділів. У Рендерингу елементів ми дізналися лише один спосіб оновлення UI. Ми викликаємо ReactDOM.render()
, щоб змінити відрендерний вивід інформації:
function tick() {
const element = (
<div>
<h1>Привіт, світе!</h1>
<h2>Зараз {new Date().toLocaleTimeString()}.</h2>
</div>
);
ReactDOM.render( element, document.getElementById('root') );}
setInterval(tick, 1000);
У цьому розділі ми дізнаємося, як зробити компонент ‘Clock’ дійсно багаторазовим та інкапсульованим. Компонент сам налаштує свій таймер та оновлюватиметься кожну секунду.
Ми можемо почати з того, як виглядає годинник:
function Clock(props) {
return (
<div> <h1>Привіт, світе!</h1> <h2>Зараз {props.date.toLocaleTimeString()}.</h2> </div> );
}
function tick() {
ReactDOM.render(
<Clock date={new Date()} />, document.getElementById('root')
);
}
setInterval(tick, 1000);
Однак, ми порушуємо важливу вимогу: той факт, що Clock
встановлює таймер і оновлює UI кожну секунду, має бути деталлю реалізації Clock
.
В ідеалі ми хочемо написати це один раз аби Clock
оновлював себе сам:
ReactDOM.render(
<Clock />, document.getElementById('root')
);
Аби це реалізувати, нам потрібно додати “стан”(“state”) до компонента Clock
.
Стан подібний до пропсів, але він приватний і повністю контролюється компонентом.
Перетворення функції на клас
Ви можете перетворити функцію компонента Clock
на клас у п’ять кроків:
- Створіть клас ES6 class з тим же ім’ям, що наслідує
React.Component
. - Додайте до нього один порожній метод, який називається
render()
. - Перемістіть тіло функції в метод
render()
. - Замініть
props
наthis.props
в тіліrender()
. - Видаліть порожні оголошення функції які залишилися.
class Clock extends React.Component {
render() {
return (
<div>
<h1>Привіт, світе!</h1>
<h2>Зараз {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>Привіт, світе!</h1>
<h2>Зараз {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>Привіт, світе!</h1>
<h2>Зараз {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>Привіт, світе!</h1>
<h2>Зараз {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>Привіт, світе!</h1>
<h2>Зараз {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>Привіт, світе!</h1>
<h2>Зараз {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 викличе метод життєвого циклуcomponentWillUnmount()
, аби таймер зупинився.
Правильно використовувати стан
Є три речі, які ви повинні знати про setState()
.
Не змінюйте стан безпосередньо
Наприклад, це не буде повторно рендерити компонент:
// Wrong
this.state.comment = 'Привіт';
Натомість використовуйте setState()
:
// Correct
this.setState({comment: 'Привіт'});
Конструктор — це єдине місце, де можна присвоїти 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>Зараз {props.date.toLocaleTimeString()}.</h2>;
}
Це зазвичай називається “зверху вниз” або “односпрямованим” потоком даних. Будь-який стан завжди належить певному компоненту і будь-які дані або UI, отримані з цього стану, можуть впливати лише на компоненти, що знаходяться “нижче” у дереві.
Уявіть дерево компонентів як водоспад пропсів, де стан кожного компонента подібний до додаткового джерела води, який приєднується до нього в довільній точці, але тече вниз.
Щоб показати, що всі компоненти є дійсно ізольованими, ми можемо створити компонент App
, який рендерить три <Clock>
:
function App() {
return (
<div>
<Clock /> <Clock /> <Clock /> </div>
);
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
Кожен Clock
встановлює свій власний таймер і оновлюється самостійно.
У додатках React, наявність або відсутність стану в компонента вважається деталлю реалізації компонента, і може змінюватися з часом. Можна використовувати компоненти без стану всередині компонентів зі станом та навпаки.