Внутрішній стан компонента
Об'єкт-стану state
– це властивість класу, яка не повинна безпосередньо
змінюватися розробником.
- Дані в
state
контролюють те, що відображається в інтерфейсі. - Дані, що зберігаються у стані, повинні бути інформацією, яка буде оновлюватися методами компонента.
- Не потрібно дублювати дані з
props
у стані. - Щоразу, коли змінюється стан компонента (або пропси), викликається метод
render()
.
У стані зберігають мінімально необхідний набір даних, на підставі яких можна
обчислити все необхідне для рендеру інтерфейсу. Це робиться викликом селекторів
(функцій, які складають дані для інтерфейсу на підставі стану) у методі
render()
. Таким чином ми отримуємо дані, що обчислюються.

- Інтерфейс залежить від стану компонента.
- Стан може змінитися як реакція на дії користувача.
- Під час зміни стану дані передаються вниз по дереву компонентів.
- Компоненти повертають оновлену розмітку і змінюється інтерфейс.
Стан належить компоненту і змінюється тільки його методами. Зміна стану компонента ніколи не вплине на його батька, сусідів або будь-який інший компонент у застосунку – тільки на його дочірні елементи. За такої моделі дані у застосунку передаються тільки одним, жорстко обмеженим чином. Це називається односпрямований потік даних.

Стан оголошується в конструкторі, оскільки це перше, що відбувається, коли створюється екземпляр класу.
class Counter extends Component {
constructor() {
super();
this.state = {
value: 0,
};
}
/* ... */
render() {
return (
<div>
<span>{this.state.value}</span>
{/* ... */}
</div>
);
}
}
Початковий стан від props
Іноді початковий стан залежить від переданих пропсів, наприклад, початкове
значення нашого лічильника. У цьому разі необхідно явно оголосити параметр
props
у конструкторі і передати його у виклик super(props)
. Тоді в
конструкторі буде доступно this.props
.
class Counter extends Component {
static defaultProps = {
step: 1,
initialValue: 0,
};
constructor(props) {
super(props);
this.state = {
value: this.props.initialValue,
};
}
/* ... */
}
ReactDOM.render(<Counter initialValue={10} />, document.getElementById("root"));
Оскільки під капотом використовується Babel, можна пропустити стомлююче оголошення конструктора і вказати стан як публічну властивість класу, все інше транспайлер зробить за нас.
class Counter extends Component {
static defaultProps = {
step: 1,
initialValue: 0,
};
state = {
value: this.props.initialValue,
};
/* ... */
}
Зміна стану компонента
Для оновлення стану використовується вбудований метод setState()
.
setState(updater, callback)
- Першим, обов'язковим аргументом, передається об'єкт з полями, які вказують, яку частину стану необхідно змінити.
- Другим, необов'язковим аргументом, можна передати callback-функцію, яка виконається після зміни стану.
Не можна змінювати стан безпосередньо за посиланням. Будьте дуже уважні, особливо під час роботи з посилальними типами (масив, об'єкт).
state = { fullName: "Poly" };
// ❌ Погано - зміна за посиланням
this.state.fullName = "Mango";
// ✅ Добре
this.setState({
fullName: "Mango",
});
Цей підхід використовується, коли новий стан не розраховується на підставі
попереднього. Тобто, коли у стан записується щось нове, перезаписуючи вже
існуюче. Зробимо компонент з перемикачем, методи якого перезаписуватимуть
значення isOpen
у стані.
class Toggle extends Component {
state = { isOpen: false };
show = () => this.setState({ isOpen: true });
hide = () => this.setState({ isOpen: false });
render() {
const { isOpen } = this.state;
const { children } = this.props;
return (
<>
<button onClick={this.show}>Show</button>
<button onClick={this.hide}>Hide</button>
{isOpen && children}
</>
);
}
}
Як оновлюється стан
Під час виклику setState()
не потрібно передавати всі властивості, що
зберігаються у стані. Достатньо вказати лише ту частину (зріз) стану, яку ми
хочемо змінити у цій операції. React потім бере поточний стан і об'єкт, який був
переданий у setState()
, об'єднуючи їх наступним чином.
// стан до об'єднання
const currentState = { a: 2, b: 3, c: 7, d: 9 };
// об'єкт, переданий в setState
const updateSlice = { b: 5, d: 4 };
// нове значення this.state після об'єднання
const nextState = { ...currentState, ...updateSlice }; // {a: 2, b: 5, c: 7, d: 4}
Асинхронність оновлення стану
Метод setState()
реєструє асинхронну операцію оновлення стану, яка ставиться в
чергу оновлень. React змінює стан не для кожного виклику setState()
, а може
об'єднувати кілька викликів в одне оновлення для підвищення продуктивності.
Внаслідок цього, доступ до this.state
у синхронному коді після виклику цього
методу поверне значення до оновлення.
Уявіть, що при зміні стану ви покладаєтеся на поточне значення стану в
обчисленні наступного. Використовуємо цикл for
для створення (реєстрації)
кількох оновлень.
// Припустимо, що є такий стан
state = { value: 0 };
// Запустимо цикл і створимо 3 операції оновлення
for (let i = 0; i < 3; i += 1) {
// Якщо переглянути стан, на всіх ітераціях буде 0
// Тому що це синхронний код та оновлення стану ще не відбулося
console.log(this.state.value);
this.setState({ value: this.state.value + 1 });
}
Значення властивості this.state.value
запам'ятовується під час створення
об'єкта, що передається в setState()
, а не під час оновлення стану. Тобто,
якщо в момент створення об'єкта this.state.value
містило 0
, у функцію
setState()
передається об'єкт {value: 0 + 1}
.
В результаті виконання циклу отримуємо чергу з 3-х об'єктів
{value: 0 + 1}, {value: 0 + 1}, {value: 0 + 1}
та оригінальний стан на момент
оновлення {value: 0}}
. Після всіх оновлень отримуємо стан {value: 1}
.
Тому не можна покладатися на поточний стан в обчисленні наступного, що залежить від попереднього на момент оновлення. Це може призвести до помилок. Тому існує другий спосіб оновити стан.
setState з функцією
Цей підхід використовується, коли нове значення обчислюється на підставі
попереднього стану. Метод setState()
першим аргументом може приймати не
об'єкт, а функцію, яка повинна повертати об'єкт, яким ми хочемо оновити стан.
setState((state, props) => {
return {};
}, callback);
Актуальний стан і пропси на момент асинхронного виконання функції, переданої в
setState()
, будуть передані в неї аргументами state
і props
. Таким чином,
можна бути впевненими у коректному значенні попереднього стану під час створення
наступного.
// Припустимо, що є такий стан
state = { value: 0 };
// Запустимо цикл і створимо 3 операції оновлення
for (let i = 0; i < 3; i += 1) {
// Якщо переглянути стан, на всіх ітераціях буде 0
// Тому що це синхронний код та оновлення стану ще не відбулося
console.log(this.state.value); // 0
this.setState(prevState => {
// Якщо переглянути стан, переданий callback-функції під час її виклику,
// отримаємо актуальний стан на момент оновлення.
console.log(prevState.value); // буде різний на кожній ітерації
return { value: prevState.value + 1 };
});
}
Щоразу під час виклику функції, переданої в setState()
, в параметр prevState
буде передане посилання на актуальний стан в момент оновлення. Отримаємо об'єкти
оновлень {value: 0 + 1}
, {value: 1 + 1}
, {value: 2 + 1}
, і, в результаті,
this.state.value
буде містити 3
.
Тепер можемо замінити функціонал відкрити/закрити у компоненті <Toggle>
.
class Toggle extends Component {
state = { isOpen: false };
toggle = () => {
this.setState(state => ({ isOpen: !state.isOpen }));
};
render() {
const { isOpen } = this.state;
const { children } = this.props;
return (
<div>
<button onClick={this.toggle}>{isOpen ? "Hide" : "Show"}</button>
{isOpen && children}
</div>
);
}
}
А лічильник виглядатиме так.
class Counter extends Component {
/* ... */
handleIncrement = () => {
this.setState((state, props) => ({
value: state.value + props.step,
}));
};
handleDecrement = () => {
this.setState((state, props) => ({
value: state.value - props.step,
}));
};
/* ... */
}
Підіймання стану
Оскільки React використовує односпрямований потік даних зверху вниз, для того щоб змінити стан батька під час події в дитині, використовується наступний патерн з callback-функцією.

- У батька є стан і метод, який його змінює.
- Дочірньому елементу у формі пропу передається метод батька, що змінює стан батька.
- У дочірньому елементі відбувається виклик переданого йому методу. – Під час виклику цього методу змінюється стан батька.
- Відбувається рендер піддерева компонентів батька.
Розглянемо простий, але наочний приклад.
// Button отримує функцію changeMessage (ім'я пропа),
// яка викликається під час події onClick
const Button = ({ changeMessage, label }) => (
<button type="button" onClick={changeMessage}>
{label}
</button>
);
class App extends Component {
state = {
message: new Date().toLocaleTimeString(),
};
// Метод, який будемо передавати в Button для виклику під час кліку
updateMessage = evt => {
console.log(evt); // Доступний об'єкт події
this.setState({
message: new Date().toLocaleTimeString(),
});
};
render() {
return (
<>
<span>{this.state.message}</span>
<Button label="Change message" changeMessage={this.updateMessage} />
</>
);
}
}
Під час кліку на кнопці стан App
оновлюється за допомогою callback-функції,
контекст якої прив'язаний до App
. Цей патерн встановлює чітку межу між
"розумними" та "дурними" компонентами.
Патерн підіймання стану може мати будь-яку вкладеність.

Типи внутрішніх даних компонента-класу
static data
– статичні властивості і методи, до яких необхідно отримувати доступ без екземпляра.this.state.data
– динамічні дані, що змінюються методами компонента, стан.this.data
– дані, які будуть різні для кожного екземпляра.const DATA
– константи, дані, які не змінюються, та однакові для всіх екземплярів.