Объект state описывает внутреннее состояние компонента, он похож на props за тем исключением, что состояние определяется внутри компонента и доступно только из компонента.
Если props представляет входные данные, которые передаются в компонент извне, то состояние хранит такие объекты, которые создаются в компоненте и полностью зависят от компонента.
Также в отличие от props значения в state можно изменять.
И еще важный момент - значения из state должны использоваться при рендеринге. Если какой-то объект не используется в рендерниге компонента, то нет смысла сохранять его в state.
Нередко state описывает какие-то визуальные свойства элемента, которые могут изменяться при взаимодействие с пользователем. Например, кнопку нажали, и соответственно можно изменить ее состояние - придать ей какой-то другой цвет, тень и так далее. Кнопку нажали повторно - можно вернуть исходное состояние.
Стоит отметить, что традиционно объект state применялся только в классах-компонентах. В функциональных же компонентах для управления состоянием применяется другая архитектура, основанная на хуках.
При использовании класса-компонента единственное место, где можно установить объект state - это конструктор класса:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Hello React</title> </head> <body> <div id="app"></div> <script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script> <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script> <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> <script type="text/babel"> class Hello extends React.Component { constructor(props) { super(props); this.state = {welcome: "Добро пожаловать на сайт!"}; } render() { return <h1>{this.state.welcome}</h1>; } } ReactDOM.createRoot( document.getElementById("app") ) .render( <Hello /> ); </script> </body> </html>
При определении конструктора компонента в нем должен вызываться конструктор базового класса, в который передается объект props
.
Для обновления состояния вызывается функция setState():
this.setState({welcome: "Привет React"});
Изменение состояния вызовет повторный рендеринг компонента, в соответствии с чем веб-страница будет обновлена.
В то же время не стоит изменять свойства состояния напрямую, например:
this.state.welcome = "Привет React";
В данном случае изменения повторного рендеринга компонента происходить не будет.
При этом нам не обязательно обновлять все его значения. В процессе работы программы мы можем обновить только некоторые свойства. Тогда необновленные свойства будут сохранять старые значения.
Пример обновления состояния:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Hello React</title> <style> button{ width: 100px; height:30px; border-radius: 4px; margin:50px; } .on{ color:#666; background-color: #ccc; } .off{ color:#888; background-color: white; } </style> </head> <body> <div id="app"></div> <script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script> <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script> <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> <script type="text/babel"> class ClickButton extends React.Component { constructor(props) { super(props); this.state = {class: "off", label: "Нажми"}; this.press = this.press.bind(this); } press(){ let className = (this.state.class==="off")?"on":"off"; this.setState({class: className}); } render() { return <button onClick={this.press} className={this.state.class}>{this.state.label}</button>; } } ReactDOM.createRoot( document.getElementById("app") ) .render( <ClickButton /> ); </script> </body> </html>
Здесь определен компонент ClickButton, который по сути представляет кнопку. В состоянии кнопки хранится два свойства - надпись и класс. При нажатии на кнопку мы
будем переключать с одного класса на другой. Событие нажатия кнопки через атрибут onClick связано с методом press()
,
в котором переключается класс кнопки.
При этом свойство state.label
остается неизменным.
При наличии нескольких вызовов setState()
React может объединять их в один общий пакет обновлений для увеличения производительности.
Так как объекты this.props
и this.state
могут обновляться асинхронно, не стоит полагаться на значения этих объектов
для вычисления состояния. Например:
this.setState({ counter: this.state.counter + this.props.increment, });
Для обновления надо использовать другую версию функции setState()
, которая в качестве параметра принимает функцию.
Данная функция имеет два параметра: предыдущее состояние объекта state и объект props на момент применения обновления:
this.setState(function(prevState, props) { return { counter: prevState.counter + props.increment }; });
Например, определим два последовательных вызова setState()
:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>METANIT.COM</title> </head> <body> <div id="app"></div> <script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script> <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script> <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> <script type="text/babel"> class ClickButton extends React.Component { constructor(props) { super(props); this.state = {counter: 0}; this.press = this.press.bind(this); } press(){ this.setState({counter: this.state.counter + parseInt(this.props.increment)}); this.setState({counter: this.state.counter + parseInt(this.props.increment)}); } render() { return <div> <button onClick={this.press}>Count</button> <div>Counter: {this.state.counter} <br />Increment: {this.props.increment}</div> </div> } } ReactDOM.createRoot( document.getElementById("app") ) .render( <ClickButton increment="1" /> ); </script> </body> </html>
В props определено свойство increment
- значение, на которое будет увеличиваться свойство counter
в state
(this.setState({counter: this.state.counter + parseInt(this.props.increment)});
). При чем при нажатии кнопки мы предполагаем, что функция setState()
будет вызываться два раза, соответственно значение state.counter
при нажатии кнопки должно увеличиваться на 2. Однако в реальности увеличение
происходит лишь на 1:
Теперь изменим код, применив второй вариант функции setState()
:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>METANIT.COM</title> </head> <body> <div id="app"></div> <script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script> <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script> <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> <script type="text/babel"> class ClickButton extends React.Component { constructor(props) { super(props); this.state = {counter: 0}; this.press = this.press.bind(this); } incrementCounter(prevState, props) { return { counter: prevState.counter + parseInt(props.increment) }; } press(){ this.setState(this.incrementCounter); this.setState(this.incrementCounter); } render() { return <div> <button onClick={this.press}>Count</button> <div>Counter: {this.state.counter}<br /> Increment: {this.props.increment}</div> </div> } } ReactDOM.createRoot( document.getElementById("app") ) .render( <ClickButton increment="1" /> ); </script> </body> </html>
Чтобы избежать повторения, все действия по инкременту вынесены в отдельную функцию - incrementCounter, однако опять же функция setState()
вызывается два раза. И теперь инкремент будет срабатывать два раза при однократном нажатии, собственно как и определено в коде и как и должно быть: