Статьи

Как работать с государством и управлять им в действии

Ниже приведен отрывок из React Quickly , практической книги Азата Мардана для тех, кто хочет быстро изучить React.js.

При обращении к состоянию, работе с ним и манипулировании им в React важно знать, что вы делаете (и почему вы это делаете!). В этой статье вы узнаете о состоянии в React и о том, как с ним работать. Мы также поговорим о некоторых различиях между состоянием и реквизитом, а также о том, как работать с компонентами без состояния. Но прежде чем мы углубимся во все это, чтобы работать с состоянием, нам нужно знать, как получить доступ к значениям, обновить их и как установить начальные значения. Давайте начнем с доступа к состоянию в компонентах React.

Исходный код примеров в этой статье можно найти в папке ch04 репозитория GitHub книги .

Доступ к государствам

Объект состояния является атрибутом компонента и доступен по this ссылке, например this.state.name . Мы можем обращаться к переменным в JSX и печатать их с помощью фигурных скобок {} . Точно так же мы можем отобразить this.state (как любую другую переменную или атрибуты класса пользовательского компонента) внутри render() . Например, {this.state.inputFieldValue} . Этот синтаксис похож на доступ к свойствам с помощью this.props.name .

Давайте продолжим и попробуем реализовать часы (рисунок 1). Цель состоит в том, чтобы иметь автономный класс компонентов, который каждый может импортировать и использовать в своем приложении без необходимости переходить через обручи. Часы должны отображать текущее время.

Часы, отображающие текущее время

Рисунок 1: Компонент Clock показывает текущее время в цифровом формате — обновляется каждую секунду

Структура проекта Clock выглядит следующим образом:

 /clock - index.html /jsx - script.jsx - clock.jsx /js - script.js - clock.js - react-15.0.2.js - react-dom-15.0.2.js 

Я использую Babel CLI с watch -w и флагом каталога -d чтобы скомпилировать все исходные JSX-файлы из clock/jsx в clock/jsx папку clock/js и перекомпилировать при изменении. Более того, я сохранил команду как скрипт npm в моем файле package.json в родительской папке с именем ch04 , чтобы запустить npm run build-clock из ch04 :

 "scripts": { "build-clock": "./node_modules/.bin/babel clock/jsx -d clock/js -w" }, 

Очевидно, что время постоянно меняется (хорошо или плохо). Из-за этого нам нужно обновить представление, используя состояние. Мы currentTime его currentTime и пытаемся отобразить это состояние, как показано в листинге 1.

 class Clock extends React.Component { render() { return <div>{this.state.currentTime}</div> } } ReactDOM.render( <Clock />, document.getElementById('content') ) 

Листинг 1: Состояние рендеринга в JSX

Если мы запустим это, мы получим следующую ошибку: Uncaught TypeError: Cannot read property 'currentTime' of null . Обычно сообщения об ошибках JavaScript так же полезны, как стакан холодной воды для тонущего человека. Хорошо, что в этом случае JavaScript дает нам полезное сообщение об ошибке. Это означает, что у нас нет никакого значения для currentTime . В отличие от реквизита, состояния не устанавливаются для родителя. Мы также не можем setState в render() , потому что он создаст круговой (setState> render> setState…) цикл, и в этом случае React выдаст ошибку.

Установка начального состояния

Вы видели, что перед использованием данных состояния в render() мы должны его инициализировать. Чтобы установить начальное состояние, используйте this.state в конструкторе с синтаксисом React.Component класса React.Component . Не забудьте вызвать super() со свойствами, иначе логика в parent ( React.Component ) не будет работать.

 class MyFancyComponent extends React.Component { constructor(props) { super(props) this.state = {...} } render() { ... } } 

Разработчики могут добавить другую логику при установке начального состояния. Например, мы можем установить значение currentTime используя new Date() . Мы даже можем использовать toLocaleString() чтобы получить правильный формат даты и времени в местоположении пользователя:

 class Clock extends React.Component { constructor(props) { super(props) this.state = {currentTime: (new Date()).toLocaleString()} } ... } 

Листинг 2: Конструктор компонента Clock (ch04 / clock)

Значение this.state должно быть объектом. Мы не будем вдаваться в подробности о ES6 constructor() , потому что есть информация в листе ES6 . Суть в том, что, как и в других языках ООП, constructor() вызывается при создании экземпляра этого класса. Имя метода конструктора должно быть constructor . Думайте об этом как соглашение ES6. Кроме того, если вы создаете метод constructor() , вам почти всегда нужно вызывать super() внутри него , иначе конструктор родителя не будет выполнен. С другой стороны, если вы не определили метод constructor() , тогда предполагается вызов super() .

Атрибуты класса

Надеемся, что TC39 (люди, стоящие за стандартом ECMAScript) добавят атрибуты к синтаксису класса в будущих версиях ECMAScript! Таким образом, разработчики могут устанавливать состояние не только в конструкторе, но и в теле класса:

 class Clock extends React.Component { state = { ... } } 

Предложение называется полями экземпляра класса или свойствами класса , но по состоянию на июль 2016 года оно доступно только с транспиляторами: Babel, Traceur или TypeScript, что означает, что ни один браузер не будет запускать эту функцию изначально. Проверьте текущую совместимость свойств класса в таблице совместимости ECMAScript .

Здесь curentTime — произвольное имя, и нам нужно будет использовать то же имя позже при доступе и обновлении этого состояния. Вы можете назвать свое государство в любом случае, если захотите, если позже к нему будете обращаться по этому имени.

Объект состояния может иметь вложенные объекты или массивы. Посмотрите на этот пример, где я добавляю массив моих книг в состояние:

 class Content extends React.Component { constructor(props) { super(props) this.state = { githubName: 'azat-co', books: [ 'pro express.js', 'practical node.js', 'rapid prototyping with js' ] } } render() { ... } } 

Метод constructor() будет вызван один раз, когда из этого класса будет создан элемент React. Таким образом, мы можем установить состояние напрямую, используя this.state , в методе constructor() . Не устанавливайте и не this.state = ... состояние напрямую с помощью this.state = ... где-либо еще, поскольку это может привести к непредвиденным последствиям.

С собственным createClass() React createClass() для определения компонента вам нужно использовать getInitialState() .

Это даст нам только первое значение, которое быстро устареет; в одну секунду Какой смысл в часах, которые не показывают текущее время? К счастью, есть способ обновить состояние.

Обновление состояний

Мы изменяем состояние с помощью метода класса this.setState(data, callback) . Когда этот метод вызывается, React объединяет данные с текущими состояниями и вызывает render() . После этого React вызывает callback .

Наличие обратного вызова в setState() важно, потому что методы работают асинхронно. Если вы полагаетесь на новое состояние, вы можете использовать функцию обратного вызова, чтобы убедиться, что это новое состояние доступно. Если вы полагаетесь на новое состояние, не дожидаясь, пока setState() завершит свою работу, т. setState() Работает синхронно с асинхронной операцией, то у вас может быть ошибка, когда состояние все еще остается старым.

Мы визуализировали время из состояния, мы также установили начальное состояние, но нам нужно обновлять время каждую секунду, верно? Мы можем использовать функцию таймера браузера setInterval() которая будет выполнять обновление состояния каждые n миллисекунд. Метод setInterval() реализован практически во всех современных браузерах как глобальный, что означает, что разработчики могут использовать его без каких-либо библиотек или префиксов.

 setInterval(()=>{ console.log('Updating time...') this.setState({ currentTime: (new Date()).toLocaleString() }) }, 1000) 

Чтобы запустить часы, нам нужно вызвать setInterval() один раз. Мы можем создать метод launchClock() для этого. Мы будем вызывать launchClock() в конструкторе. Последние часы могут выглядеть так, как показано в листинге 3.

Листинг 3: Реализация часов с состоянием React и setInterval() (ch04 / clock / jsx / clock.jsx).

 class Clock extends React.Component { constructor(props) { super(props) this.launchClock() <1> this.state = { currentTime: (new Date()).toLocaleString() <2> } } launchClock() { setInterval(()=>{ console.log('Updating time...') this.setState({ currentTime: (new Date()).toLocaleString() <3> }) }, 1000) <4> } render() { console.log('Rendering Clock...') return <div>{this.state.currentTime}</div> <5> } } 

<1> Trigger launchClock()
<2> Установить начальное состояние на текущее время
<3> Обновлять состояние с текущим временем каждую секунду
<4> Привязать контекст для ссылки на экземпляр компонента
<5> состояние рендеринга

Вы можете использовать setState() где угодно, а не только в launchClock() (которая вызывается constructor ), как показано в примере. Как правило, setState() вызывается из обработчика событий или как обратный вызов для входящих данных или обновлений данных.

Изменение значения состояния в вашем коде, например this.state.name= 'new name' , не поможет. Это не вызовет повторную визуализацию и возможное реальное обновление DOM, которое мы хотим. По большей части изменение состояния напрямую без setState является анти-паттерном и его следует избегать.

Важно отметить, что setState() обновляет только те состояния, которые вы передаете (частичное или слияние, но не полная замена). Он не заменяет весь объект состояния каждый раз. Если у вас есть три состояния, а затем измените одно, два других остаются без изменений. В приведенном ниже примере userEmail и userId останутся без изменений:

 constructor(props) { super(props) this.state = { userName: , userEmail: '[email protected]', userId: 3967 } } updateValues() { this.setState({userName: 'Azat'}) } 

Если вы намереваетесь обновить все три состояния, то вам нужно сделать это явно, передав новые значения для этих состояний в setState() . Другой метод, который иногда встречается в старом коде React, но который больше не работает и устарел, это this.replaceState() . Как видно из названия, он заменил весь объект состояния всеми его атрибутами.

Имейте в виду, что setState() вызывает render() . Это работает в большинстве случаев. В некоторых сценариях пограничного случая, когда код зависит от внешних данных, вы можете запустить повторную визуализацию с помощью this.forceUpdate() , но этого подхода следует избегать, поскольку он опирается на внешние данные, а не на состояние, делая компоненты более хрупкими и зависит от внешних факторов (жесткая связь).

Как упоминалось ранее, вы можете получить доступ к объекту состояния с помощью this.state . Если вы помните, мы выводим значения с помощью фигурных скобок ( {} ); поэтому, чтобы объявить свойство состояния в представлении (оператор return render ), примените this.state.NAME .

Реагирование происходит, когда вы используете данные состояния в представлении (например, для печати в if/else в качестве значения атрибута или в качестве значения свойства setState() ), а затем присваиваете setState() новые значения. Boom! Реакт обновляет HTML для вас. Вы можете наблюдать это в своей консоли DevTools. Он должен показывать циклы обновления … и затем рендеринга … И, что самое приятное, это касается ТОЛЬКО минимально необходимых элементов DOM.

Связать это в JavaScript

В JavaScript this изменяет (изменяет) свое значение в зависимости от того, откуда вызывается функция. Чтобы убедиться, что this относится к нашему классу компонента, нам нужно привязать функцию к соответствующему контексту (это значение является нашим классом компонента).

Если вы используете ES6 + / ES2015 +, как я здесь делаю, вы можете использовать синтаксис жирной стрелки для создания функции с автосвязыванием:

 setInterval(()=>{ this.setState({ currentTime: (new Date()).toLocaleString() }) }, 1000) 

Автосвязывание означает, что функция, созданная жирными стрелками, получит текущее значение this , в нашем случае Clock .

Ручной подход заключается в использовании метода bind(this) для замыкания:

 function() {...}.bind(this) 

Или для наших часов:

 setInterval(function(){ this.setState({ currentTime: (new Date()).toLocaleString() }) }.bind(this), 1000) 

Это поведение не является эксклюзивным для React. Ключевое слово this изменяется в закрытии функции, и нам нужно либо связать его, либо сохранить значение контекста ( this ) для дальнейшего использования. Как правило, мы видим переменные типа self , that или _this используемые для сохранения значения оригинала this . Большинство из вас, вероятно, видели такие заявления:

 var that = this var _this = this var self = this 

Идея проста; Вы создаете переменную и используете ее в замыкании вместо ссылки на this . Новая переменная будет не копией, а ссылкой на оригинал this значения. Вот наш setInterval() :

 var _this = this setInterval(function(){ _this.setState({ currentTime: (new Date()).toLocaleString() }) }, 1000) 

У нас есть наши часы, и они работают (рисунок 2). Tadaaa!

Часы тикают

Рисунок 2: Часы тикают

Одна вещь, прежде чем мы продолжим. Вы можете увидеть, как React использует один и тот же элемент DOM <div> и изменяет только текст внутри него. Идите дальше и используйте DevTools для изменения CSS этого элемента. Я добавил стиль, чтобы сделать текст синим ( color: blue ), как показано на рисунке 3. Он создал встроенный стиль, а не класс. Элемент и его новый встроенный стиль остались теми же (синие), что и время, которое тикало.

React обновляет время как текст, а не элемент div

Рисунок 3: React обновляет время как текст, а не элемент div (вручную добавлен цвет: синий)

React обновит только внутренний HTML (содержимое второго контейнера <div> ). <div> и все остальные элементы на этой странице остаются без изменений . Ухоженная. 😉

Штаты и свойства

Состояния и свойства являются атрибутами для класса, то есть они this.state и this.props . Это единственное сходство! Одно из основных различий между свойствами и состоянием заключается в том, что первое является неизменным, а второе изменяемым.

Другое различие между свойствами и состояниями состоит в том, что мы передаем свойства от родительских компонентов, поскольку определяем состояния в самом компоненте, а не в его родительском компоненте. Философия здесь заключается в том, что вы можете изменять значение свойства только из родительского элемента, а не сам компонент. Свойства определяют вид при создании, а затем они остаются статичными (они не меняются). Состояние, с другой стороны, устанавливается и обновляется самим объектом.

Реквизиты и состояния служат разным целям, но оба доступны как атрибуты класса компонента, и оба помогают разработчикам составлять компоненты с различным представлением (представлением). Существуют многочисленные различия между реквизитом и состоянием, когда дело доходит до жизненного цикла компонента. Рассматривайте реквизиты и состояния как входные данные для функции, которая выдает различные выходные данные Эти выводы являются представлениями. Вы можете иметь различные пользовательские интерфейсы (представления) для каждого набора реквизитов и состояний (рисунок 4).

Новые значения для реквизита и состояний могут изменить пользовательский интерфейс

Рисунок 4: Новые значения для реквизитов и состояний могут изменить пользовательский интерфейс, но для реквизитов новые значения исходят от родителя, а для состояния — от самого компонента

Не все компоненты должны иметь состояние. Давайте посмотрим, как использовать свойства с компонентами без сохранения состояния.

Компоненты без состояния

Концепция компонента без состояния — это компонент, который не имеет ни состояний, ни компонентов, ни каких-либо других событий / методов жизненного цикла React. Назначение компонента без состояния состоит в визуализации представления. Единственное, что он может сделать — это взять свойства и что-то с ними сделать — простую функцию с входом (свойством) и выходом (элементом пользовательского интерфейса).

Преимущество использования компонентов без сохранения состояния заключается в том, что они предсказуемы, потому что у нас есть один вход, который определяет вывод. Предсказуемость означает, что их легче понять, поддерживать и отлаживать. Фактически, отсутствие состояния является наиболее желательной практикой React — чем больше компонентов без состояния вы используете и чем меньше «состояний», тем лучше.

Этот скрипт Hello World является хорошим примером компонента без сохранения состояния (листинг 4):

 class HelloWorld extends React.Component { render() { return <h1 {...this.props}>Hello {this.props.frameworkName} World!!!</h1> } } 

Листинг 4 (ch03 / hello-js-world-jsx / jsx / script.jsx)

Чтобы иметь меньший синтаксис для компонентов без состояний, React предоставляет нам стиль функции. Мы создаем функцию, которая принимает свойства в качестве аргумента и возвращает представление. Компонент без состояния отображается как любой другой компонент. Например, компонент HelloWorld может быть переписан как функция, которая возвращает <h1> :

 const HelloWorld = function(props){ return <h1 {...props}>Hello {props.frameworkName} world!!!</h1> } 

Примечание: да. Вы можете использовать функции стрелок ES6 + / ES2015 + для компонентов без состояния. Следующий фрагмент аналогичен приведенному выше фрагменту (возврат также может быть опущен, но мне нравится иметь его):

 const HelloWorld = (props)=>{ return <h1 {...props}>Hello {props.frameworkName} world!!!</h1> } 

Как видно, разработчики также могут определять функции как компоненты React, когда нет необходимости в состоянии. Чтобы создать компонент без состояния, просто определите его как функцию. Другой пример, в котором Link является компонентом без сохранения состояния:

 function Link (props) { return <a href={props.href} target="_blank" className="btn btn-primary">{props.text}</a> } ReactDOM.render( <Link text='Buy React Quickly' href='https://www.manning.com/books/react-quickly'/>, document.getElementById('content') ) 

Нет необходимости в автосвязывании, но мы можем использовать синтаксис функции жирных стрелок для краткости (когда есть один оператор, нотация может быть однострочной):

 const Link = props=> <a href={props.href} target="_blank" className="btn btn-primary">{props.text}</a> 

В компоненте без состояния у нас не может быть состояния, но у нас может быть два свойства: propTypes и defaultProps . Мы устанавливаем их на объекте:

 function Link (props) { return <a href={props.href} target="_blank" className="btn btn-primary">{props.text}</a> } Link.propTypes = {...} Link.defaultProps = {...} 

Мы также не можем использовать ссылки ( refs ) с функциями без сохранения состояния. Если вам нужно использовать refs , вы можете обернуть компонент без состояния в обычный компонент React.

Вывод

В этой статье я представил состояние в React и продемонстрировал, как с ним работать. Я рассмотрел некоторые различия между состоянием и реквизитом, а также то, как работать с так называемыми компонентами без состояния.

И это все на данный момент — надеюсь, это дало вам лучшее понимание работы с состояниями в React. Чтобы узнать больше о React и его многочисленных применениях, обратитесь к книге « React Quickly» .