Стан та життєвий цикл
These docs are old and won’t be updated. Go to react.dev for the new React docs.
These new documentation pages teach modern React and include live examples:
На цій сторінці представлено поняття стану та життєвого циклу у React-компоненті. Ви можете знайти детальний API-довідник по компонентах тут.
Розглянемо приклад відліку годинника з одного з попередніх розділів. У Рендерингу елементів ми дізналися лише один спосіб оновлення UI. Ми викликаємо root.render()
, щоб змінити відрендерний вивід інформації:
const root = ReactDOM.createRoot(document.getElementById('root'));
function tick() {
const element = (
<div>
<h1>Привіт, світе!</h1>
<h2>Зараз {new Date().toLocaleTimeString()}.</h2>
</div>
);
root.render(element);}
setInterval(tick, 1000);
У цьому розділі ми дізнаємося, як зробити компонент ‘Clock’ дійсно багаторазовим та інкапсульованим. Компонент сам налаштує свій таймер та оновлюватиметься кожну секунду.
Ми можемо почати з того, як виглядає годинник:
const root = ReactDOM.createRoot(document.getElementById('root'));
function Clock(props) {
return (
<div> <h1>Привіт, світе!</h1> <h2>Зараз {props.date.toLocaleTimeString()}.</h2> </div> );
}
function tick() {
root.render(<Clock date={new Date()} />);}
setInterval(tick, 1000);
Однак, ми порушуємо важливу вимогу: той факт, що Clock
встановлює таймер і оновлює UI кожну секунду, має бути деталлю реалізації Clock
.
В ідеалі ми хочемо написати це один раз аби Clock
оновлював себе сам:
root.render(<Clock />);
Аби це реалізувати, нам потрібно додати “стан”(“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 />
:
root.render(<Clock />);
Пізніше ми додамо код таймера назад до самого компонента.
Результат виглядає так:
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>
);
}
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Clock />);
Далі ми зробимо так, аби 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>
);
}
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Clock />);
Тепер годинник тікає кожну секунду.
Давайте швидко повторимо, що відбувається, і порядок, в якому ці методи викликаються:
- Коли
<Clock />
передається доroot.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>
);
}
Кожен Clock
встановлює свій власний таймер і оновлюється самостійно.
У додатках React, наявність або відсутність стану в компонента вважається деталлю реалізації компонента, і може змінюватися з часом. Можна використовувати компоненти без стану всередині компонентів зі станом та навпаки.