Внутрішній стан компонента
Об'єкт-стану 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– константи, дані, які не змінюються, та однакові для всіх екземплярів.