Состояние. Управление компонентами-классами
Объект state описывает внутреннее состояние компонента, он похож на props за тем исключением, что состояние определяется внутри компонента и доступно только из компонента.
Если props представляет входные данные, которые передаются в компонент извне, то состояние хранит такие объекты, которые создаются в компоненте и полностью зависят от компонента.
Также в отличие от props значения в state можно изменять.
И еще важный момент — значения из state должны использоваться при рендеринге. Если какой-то объект не используется в рендерниге компонента, то нет смысла сохранять его в state.
Нередко state описывает какие-то визуальные свойства элемента, которые могут изменяться при взаимодействие с пользователем. Например, кнопку нажали, и соответственно можно изменить ее состояние — придать ей какой-то другой цвет, тень и так далее. Кнопку нажали повторно — можно вернуть исходное состояние.
Стоит отметить, что традиционно объект state применялся только в классах-компонентах. В функциональных же компонентах для управления состоянием применяется другая архитектура, основанная на хуках.
При использовании класса-компонента единственное место, где можно установить объект state — это конструктор класса:
Hello React
При определении конструктора компонента в нем должен вызываться конструктор базового класса, в который передается объект props .

Обновление состояния
Для обновления состояния вызывается функция setState() :
this.setState();
Изменение состояния вызовет повторный рендеринг компонента, в соответствии с чем веб-страница будет обновлена.
В то же время не стоит изменять свойства состояния напрямую, например:
this.state.welcome = "Привет React";
В данном случае изменения повторного рендеринга компонента происходить не будет.
При этом нам не обязательно обновлять все его значения. В процессе работы программы мы можем обновить только некоторые свойства. Тогда необновленные свойства будут сохранять старые значения.
Пример обновления состояния:
Hello React button < width: 100px; height:30px; border-radius: 4px; margin:50px; >.on < color:#666; background-color: #ccc; >.off
Здесь определен компонент 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() :
METANIT.COM
В props определено свойство increment — значение, на которое будет увеличиваться свойство counter в state ( this.setState(); ). При чем при нажатии кнопки мы предполагаем, что функция setState() будет вызываться два раза, соответственно значение state.counter при нажатии кнопки должно увеличиваться на 2. Однако в реальности увеличение происходит лишь на 1:

Теперь изменим код, применив второй вариант функции setState() :
METANIT.COM
Чтобы избежать повторения, все действия по инкременту вынесены в отдельную функцию — incrementCounter, однако опять же функция setState() вызывается два раза. И теперь инкремент будет срабатывать два раза при однократном нажатии, собственно как и определено в коде и как и должно быть:
Состояние компонента
Метод setState() планирует изменение объекта состояния ( state ) компонента. Когда состояние меняется, компонент рендерится повторно.
Какая разница между state и props ?
props (намеренно сокращённо от англ. «properties» — свойства) и state — это обычные JavaScript-объекты. Несмотря на то, что оба содержат информацию, которая влияет на то, что увидим после рендера, есть существенное различие: props передаётся в компонент (служат как параметры функции), в то время как state находится внутри компонента (по аналогии с переменными, которые объявлены внутри функции).
Несколько полезных ресурсов для дальнейшего изучения, в каких случаях использовать props , а в каких — state :
- Props vs. State
- ReactJS: Props vs State
Почему setState даёт неверное значение?
В React как this.props , так и this.state представляют значения, которые уже были отрендерены, например, то, что видите на экране.
Вызовы setState являются асинхронными, поэтому не стоит рассчитывать, что this.state отобразит новое значение мгновенно после вызова setState . Необходимо добавить функцию, которая сработает только после обновления состояния, если нужно получить новое значение, основанное на текущем состоянии (ниже подробный пример).
Пример кода, который не будет работать так, как ожидаем:
incrementCount() // Примечание: это *не* сработает, как ожидалось. this.setState(count: this.state.count + 1>); > handleSomething() // Допустим, что `this.state.count` начинается с 0. this.incrementCount(); this.incrementCount(); this.incrementCount(); // Когда React делает последующий рендер компонента, `this.state.count` будет 1, хотя мы ожидаем 3. // Так происходит, потому что функция `incrementCount()` берёт своё значение из `this.state.count`, // но React не обновляет `this.state.count`, пока компонент не отрендерится снова. // Получается, что `incrementCount()` обращается к текущему значению `this.state.count`, а это 0 каждый раз, и добавляет 1. // Как исправить это — разберём ниже! >
Далее перейдём к исправлению указанной проблемы.
Как обновить состояние значениями, которые зависят от текущего состояния?
Нужно добавить функцию вместо объекта к setState , которая будет срабатывать только на самой последней версии состояния (пример ниже).
В чём разница между добавлением объекта или функции к setState ?
Добавление функции даёт вам доступ к текущему состоянию внутри самой функции. Так как setState вызовы «сгруппированы», это помогает связать изменения и гарантирует, что они будут выполняться друг за другом, а не конфликтовать.
incrementCount() this.setState((state) => // Важно: используем `state` вместо `this.state` при обновлении. return count: state.count + 1> >); > handleSomething() // Возьмём снова для примера, что `this.state.count` начинается с 0. this.incrementCount(); this.incrementCount(); this.incrementCount(); // Если посмотреть на значение `this.state.count` сейчас, это будет по-прежнему 0. // Но когда React отрендерит компонент снова, будет уже 3. >
Когда setState работает асинхронно?
В настоящее время setState работает асинхронно внутри обработчиков событий.
Это даёт гарантию, например, когда Родитель и Ребёнок вызывают setState во время клика, Ребёнок не будет рендериться дважды. Вместо этого React «откладывает» обновление состояния в самый конец событий в браузере. Это помогает сильно повысить производительность больших приложений.
Но не стоит полностью полагаться на такое поведение. В будущих версиях React будет использовать отложенные обновления состояния по умолчанию не только в обработчиках событий.
Почему React не обновляет this.state синхронно?
Как говорилось ранее, React намеренно «ждёт» пока все компоненты вызовут setState() в своих обработчиках событий прежде чем начать повторный рендер. Это избавляет от ненужных повторных рендеров.
Вы можете задаваться вопросом: почему React не может просто сразу обновить this.state без повторного рендеринга?
На это есть две причины:
- Это нарушит логику работы props и state , а значит станет причиной многих багов, которые будет сложно исправить.
- Это сделало бы невозможным реализацию некоторых возможностей, над которыми мы сейчас работаем.
Этот GitHub-комментарий рассматривает конкретные примеры, которые помогут глубже изучить этот вопрос.
Стоит ли использовать такие библиотеки, как Redux или MobX?
Но вообще будет здорово сначала изучить React, прежде чем переходить к библиотекам. Можно создать готовое рабочее приложение, используя только React.
Подъём состояния
Часто несколько компонентов должны отражать одни и те же изменяющиеся данные. Мы рекомендуем поднимать общее состояние до ближайшего общего предка. Давайте посмотрим, как это работает.
В этом разделе мы создадим калькулятор температуры, вычисляющий вскипит ли вода при заданной температуре.
Мы начнём с компонента под названием BoilingVerdict . Он принимает температуру по шкале Цельсия в качестве пропа celsius и выводит, достаточна ли температура для кипения воды:
function BoilingVerdict(props) if (props.celsius >= 100) return p>Вода закипит.p>; > return p>Вода не закипит.p>;>
Затем мы создадим компонент Calculator . Он рендерит для ввода температуры и сохраняет её значение в this.state.temperature .
Кроме того, он рендерит BoilingVerdict для текущего значения поля ввода.
class Calculator extends React.Component constructor(props) super(props); this.handleChange = this.handleChange.bind(this); this.state = temperature: ''>; > handleChange(e) this.setState(temperature: e.target.value>); > render() const temperature = this.state.temperature; return ( fieldset> legend>Введите температуру в градусах Цельсия:legend> input value=temperature> onChange=this.handleChange> /> BoilingVerdict celsius=parseFloat(temperature)> /> fieldset> ); > >
Добавление второго поля ввода
Добавим к полю ввода градусов Цельсия поле ввода по шкале Фаренгейта. Оба поля будут синхронизироваться.
Мы можем начать с извлечения компонента TemperatureInput из Calculator . Добавим в него новый проп scale , значением которого может быть либо «c» или «f» :
const scaleNames = c: 'Цельсия', f: 'Фаренгейта'>; class TemperatureInput extends React.Component constructor(props) super(props); this.handleChange = this.handleChange.bind(this); this.state = temperature: ''>; > handleChange(e) this.setState(temperature: e.target.value>); > render() const temperature = this.state.temperature; const scale = this.props.scale; return ( fieldset> legend>Введите температуру в градусах scaleNames[scale]>:legend> input value=temperature> onChange=this.handleChange> /> fieldset> ); > >
Теперь можем изменить Calculator для рендера двух отдельных полей ввода температуры:
class Calculator extends React.Component render() return ( div> TemperatureInput scale="c" /> TemperatureInput scale="f" /> div> ); > >
Сейчас у нас есть два поля ввода, но когда вы вводите температуру в одно из них, другое поле не обновляется. Это противоречит нашему требованию — мы хотим их синхронизировать.
Мы также не можем отображать BoilingVerdict из Calculator . Компонент Calculator не знает текущую температуру, потому что она находится внутри TemperatureInput .
Написание функций для конвертации температур
Во-первых, мы напишем две функции для конвертации градусов по шкале Цельсия в Фаренгейт и обратно:
function toCelsius(fahrenheit) return (fahrenheit - 32) * 5 / 9; > function toFahrenheit(celsius) return (celsius * 9 / 5) + 32; >
Эти две функции конвертируют числа. Мы напишем ещё одну функцию, которая принимает строку с температурой ( temperature ) и функцию конвертации ( convert ) в качестве аргументов, и возвращает строку. Мы будем использовать эту функцию для вычисления значения одного поля ввода на основе значения из другого поля ввода.
Данная функция возвращает пустую строку при некорректном значении аргумента temperature и округляет возвращаемое значение до трёх чисел после запятой:
function tryConvert(temperature, convert) const input = parseFloat(temperature); if (Number.isNaN(input)) return ''; > const output = convert(input); const rounded = Math.round(output * 1000) / 1000; return rounded.toString(); >
Например, вызов tryConvert(‘abc’, toCelsius) возвратит пустую строку, а вызов tryConvert(‘10.22’, toFahrenheit) — ‘50.396’ .
В настоящее время оба компонента TemperatureInput независимо хранят свои значения каждое в собственном локальном состоянии:
class TemperatureInput extends React.Component constructor(props) super(props); this.handleChange = this.handleChange.bind(this); this.state = temperature: ''>; > handleChange(e) this.setState(temperature: e.target.value>); > render() const temperature = this.state.temperature; // .
Однако мы хотим, чтобы эти два поля ввода синхронизировались друг с другом. Когда мы обновляем поле ввода градусов по Цельсию, поле ввода градусов по Фаренгейту должно отражать сконвертированную температуру и наоборот.
В React совместное использование состояния достигается перемещением его до ближайшего предка компонентов, которым оно требуется. Это называется «подъём состояния». Мы удалим внутреннее состояние из TemperatureInput и переместим его в Calculator .
Если Calculator владеет общим состоянием, он становится «источником истины» текущей температуры для обоих полей ввода. Он может предоставить им значения, которые не противоречат друг другу. Поскольку пропсы обоих компонентов TemperatureInput приходят из одного и того же родительского компонента Calculator , два поля ввода будут всегда синхронизированы.
Давайте шаг за шагом посмотрим, как это работает.
Во-первых, мы заменим this.state.temperature на this.props.temperature в компоненте TemperatureInput . Пока давайте представим, что this.props.temperature уже существует, хотя нам нужно будет передать его из Calculator в будущем:
render() // Ранее было так: const temperature = this.state.temperature; const temperature = this.props.temperature; // .
Мы знаем, что пропсы доступны только для чтения. Когда temperature находилась во внутреннем состоянии, TemperatureInput мог просто вызвать this.setState() для изменения его значения. Однако теперь, когда temperature приходит из родительского компонента в качестве пропа, TemperatureInput не может контролировать его.
В React это обычно решается путём создания «управляемого» компонента. Точно так же, как DOM-элемент принимает атрибуты value и onChange , так и пользовательский TemperatureInput принимает оба пропса temperature и onTemperatureChange от своего родителя Calculator .
Теперь, когда TemperatureInput хочет обновить свою температуру, он вызывает this.props.onTemperatureChange :
handleChange(e) // Ранее было так: this.setState(); this.props.onTemperatureChange(e.target.value); // .
Примечание:
В пользовательских компонентах нет особого смысла в именах пропсов temperature или onTemperatureChange . Мы могли бы назвать их как-то иначе, например, value и onChange , т. к. подобные имена — распространённое соглашение.
Пропсы onTemperatureChange и temperature будут предоставлены родительским компонентом Calculator . Он будет обрабатывать изменения, модифицируя собственное внутреннее состояние, тем самым повторно отрендеривая оба поля ввода с новыми значениями. Вскоре мы рассмотрим новую реализацию Calculator .
Прежде чем изменить Calculator , давайте вспомним, что поменялось в компоненте TemperatureInput . Мы удалили из него внутреннее состояние, и вместо this.state.temperature теперь используем this.props.temperature . Вместо вызова this.setState() , когда мы хотим изменить состояние, теперь вызываем this.props.onTemperatureChange() , который получен от компонента Calculator :
class TemperatureInput extends React.Component constructor(props) super(props); this.handleChange = this.handleChange.bind(this); > handleChange(e) this.props.onTemperatureChange(e.target.value); > render() const temperature = this.props.temperature; const scale = this.props.scale; return ( fieldset> legend>Введите градусы по шкале scaleNames[scale]>:legend> input value=temperature> onChange=this.handleChange> /> fieldset> ); > >
Теперь перейдём к компоненту Calculator .
Мы будем хранить текущие значения temperature и scale во внутреннем состоянии этого компонента. Это состояние, которое мы «подняли» от полей ввода, и теперь оно будет служить «источником истины» для них обоих. Это минимальное представление всех данных, про которое нам нужно знать для рендера обоих полей ввода.
Например, если мы вводим 37 как значение поля ввода для температуры по шкале Цельсия, состояние компонента Calculator будет:
temperature: '37', scale: 'c' >
Если позднее мы изменим поле для ввода градусов по шкале Фаренгейта на 212, состояние Calculator будет:
temperature: '212', scale: 'f' >
Мы могли бы сохранить значения обоих полей ввода, но это оказалось бы ненужным. Достаточно сохранить значение последнего изменённого поля ввода и шкалу, которая это значение представляет. Затем мы можем вывести значение для другого поля ввода, основываясь только на текущих значениях temperature и scale .
Поля ввода остаются синхронизированными, поскольку их значения вычисляются из одного и того же состояния:
class Calculator extends React.Component constructor(props) super(props); this.handleCelsiusChange = this.handleCelsiusChange.bind(this); this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this); this.state = temperature: '', scale: 'c'>; > handleCelsiusChange(temperature) this.setState(scale: 'c', temperature>); > handleFahrenheitChange(temperature) this.setState(scale: 'f', temperature>); > render() const scale = this.state.scale; const temperature = this.state.temperature; const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature; const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature; return ( div> TemperatureInput scale="c" temperature=celsius> onTemperatureChange=this.handleCelsiusChange> /> TemperatureInput scale="f" temperature=fahrenheit> onTemperatureChange=this.handleFahrenheitChange> /> BoilingVerdict celsius=parseFloat(celsius)> /> div> ); > >
Теперь, независимо от того, какое поле ввода вы редактируете, this.state.temperature и this.state.scale в Calculator обновляются. Одно из полей ввода получает значение как есть, поэтому введённые пользователем данные сохраняются, а значение другого поля ввода всегда пересчитывается на их основе.
Давайте посмотрим, что происходит, когда вы редактируете поле ввода:
- React вызывает функцию, указанную в onChange на DOM-элементе . В нашем случае это метод handleChange() компонента TemperatureInput .
- Метод handleChange() в компоненте TemperatureInput вызывает this.props.onTemperatureChange() с новым требуемым значением. Его пропсы, включая onTemperatureChange , были предоставлены его родительским компонентом — Calculator .
- Когда Calculator рендерился ранее, он указал, что onTemperatureChange в компоненте TemperatureInput по шкале Цельсия — это метод handleCelsiusChange в компоненте Calculator , а onTemperatureChange компонента TemperatureInput по шкале Фаренгейта — это метод handleFahrenheitChange в компоненте Calculator . Поэтому один из этих двух методов Calculator вызывается в зависимости от того, какое поле ввода редактируется.
- Внутри этих методов компонент Calculator указывает React сделать повторный рендер себя, используя вызов this.setState() со значением нового поля ввода и текущей шкалой.
- React вызывает метод render() компонента Calculator , чтобы узнать, как должен выглядеть UI. Значения обоих полей ввода пересчитываются исходя из текущей температуры и шкалы. В этом методе выполняется конвертация температуры.
- React вызывает методы render() конкретных компонентов TemperatureInput с их новыми пропсами, переданными компонентом Calculator . Он узнает, как должен выглядеть UI.
- React вызывает метод render() компонента Boiling Verdict , передавая температуру в градусах Цельсия как проп.
- React DOM обновляет DOM, чтобы привести его в соответствие с нужными нам значениями в полях ввода. Отредактированное нами только что поле ввода получает его текущее значение, а другое поле ввода обновляется конвертированным значением температуры.
Каждое обновление проходит через одни и те же шаги, поэтому поля ввода остаются синхронизированными.
Для любых изменяемых данных в React-приложении должен быть один «источник истины». Обычно состояние сначала добавляется к компоненту, которому оно требуется для рендера. Затем, если другие компоненты также нуждаются в нём, вы можете поднять его до ближайшего общего предка. Вместо того, чтобы пытаться синхронизировать состояние между различными компонентами, вы должны полагаться на однонаправленный поток данных.
Для подъёма состояния приходится писать больше «шаблонного» кода, чем при подходах с двусторонней привязкой данных, но мы получаем преимущество в виде меньших затрат на поиск и изолирование багов. Так как любое состояние «живёт» в каком-нибудь компоненте, и только этот компонент может его изменить, количество мест с возможными багами значительно уменьшается. Кроме того, вы можете реализовать любую пользовательскую логику для отклонения или преобразования данных, введённых пользователем.
Если что-то может быть вычислено из пропсов или из состояния, то скорее всего оно не должно находиться в состоянии. Например, вместо сохранения celsiusValue и fahrenheitValue , мы сохраняем только последнюю введённую температуру ( temperature ) и её шкалу ( scale ). Значение другого поля ввода можно всегда вычислить из них в методе render() . Это позволяет очистить или применить округление к значению другого поля, не теряя при этом точности значений, введённых пользователем.
Когда вы видите, что в UI что-то отображается неправильно, то можете воспользоваться расширением React Developer Tools. С помощью него можно проверить пропсы и перемещаться по дереву компонентов вверх до тех пор, пока не найдёте тот компонент, который отвечает за обновление состояния. Это позволяет отследить источник багов:
State в react что это
Инструменты
Общие инструменты
- Оптимизатор изображений
- Генератор паролей
- HTML редактор
- Кодер/Декодер HTML
- Base 64
- Сравнение кодов
- CSS Генератор
- CSS Генератор Text Shadow
- CSS Генератор Text Rotation
- CSS Генератор Outline
- CSS Генератор RGB Shadow
- CSS Генератор Transform
- CSS Генератор Font Face
Инструменты цвета
- Color Picker
- Colors CMYK
- Colors HWB
- Colors HSL
- Color Hex
- Color mixer
- Color Converter
- Colors RGB
- Color Contrast Analyzer
Струнные инструменты
- Вычисление длины строки
- Строка для MD5 генератора хеша
- Строка для SHA-256 хеш калькулятора
- Реверс строки
- URL кодер
- URL декодер
- Base 64 кодер
- Base 64 декодер
- Удаление лишних пробелов
- Конвертер нижнего регистра
- Конвертер верхнего регистра
- Подсчет слов в строке
- Удаление пустых строк
- Удаление HTML тегов
- Конвертер двоичных данных в Hex
- Декодер двоичной строки, закодированной в Hex
- Конвертер Rot13 в текст
- Конвертер строки в двоичную форму
- Удаление повторяющихся строк
- CSS Maker
- CSS Maker Text Shadow
- CSS Maker Text Rotation
- CSS Maker Outline
- CSS Maker RGB Shadow
- CSS Maker Transform
- CSS Maker Font Face

- Тесты
- Основы HTML
- Основы CSS
- Основы Javascript
- Основы PHP
- Основы ES6
- Основы TypeScript
- Основы Angular
- Основы React
- Основы Sass
- Основы Vue.js
- Основы Python
- Основы Git
- Основы SQL
- Основы Java
- Как Git
- Как AngularJs
- Как Linux
- Как HTML
- Как CSS
- Как JavaScript
- Как Java
- Учебник HTML
- Учебник CSS
- Учебник Javascript
- Оптимизатор изображений
- Color Picker
- Кодер/Декодер HTML
- HTML редактор
- CSS Генератор
- Генератор паролей
- Base 64
- Сравнение кодов
- Вычисление длины строки
- Строка для MD5 генератора хеша
- Строка для SHA-256 хеш калькулятора
- Реверс строки
- URL кодер
- URL декодер
- Base 64 кодер
- Base 64 декодер
- Удаление лишних пробелов
- Конвертер нижнего регистра
- О нас
- Политика конфиденциальности W3Docs
Присоединяйтесь к нам
Компания © W3docs. Все права защищены.