Pull to refresh

Книга «React быстро. Веб-приложения на React, JSX, Redux и GraphQL»

Reading time9 min
Views15K
image Привет, Хаброжители! Оригинальное издание вышло осенью 2017 года, но до сих пор считается лучшей книгой для знакомства с React. Автор постоянно обновляет и дорабатывает код к книги в репозитории Github.

Предлагаем в посте ознакомится с отрывком «Состояния и их роль в интерактивной природе React»

Если бы вам пришлось прочитать в этой книге всего одну главу — стоило бы выбрать именно эту! Без состояний компоненты React остаются не более чем усовершенствованными статическими шаблонами. Надеюсь, вы разделяете мой энтузиазм, потому что понимание концепций этой главы позволит вам строить намного более интересные приложения.


Представьте, что вы строите поле ввода с автозаполнением (рис. 4.1). При вводе данных поле должно выдать запрос к серверу, чтобы получить информацию о подходящих вариантах для отображения вывода на веб-странице. До сих пор вы работали со свойствами, и знаете, что изменение свойств позволяет получить разные представления. Однако свойства не могут изменяться в контексте текущего компонента, потому что они передаются при создании компонента.

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

Одно из возможных решений — рендерить элемент с новыми свойствами каждый раз, когда вы получаете новый ответ от сервера. Но тогда вам придется разместить логику за пределами компонента — и компонент перестает быть самодостаточным. Очевидно, если значения свойств нельзя изменять, а автозаполнение должно быть самодостаточным, использовать свойства невозможно. Тогда возникает вопрос: как обновлять представления в ответ на события без повторного создания компонента (createElement() или JSX <NAME/>)? Именно эту проблему решают состояния.

image

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

С состоянием компонентов React вы можете строить интерактивные, содержательные приложения React. Состояние — основополагающая концепция, которая позволяет строить компоненты React, способные хранить данные и автоматически обновлять представления в соответствии с изменениями в данных.

Что такое состояние компонента React?


Состояние React представляет собой изменяемое хранилище данных компонента — автономные функционально-ориентированные блоки пользовательского интерфейса и логики. «Изменяемость» означает, что значения состояний могут изменяться. Используя состояние в представлении (render()) и изменяя значения позднее, вы можете влиять на внешний вид представления.

Метафора: если представить себе компонент в виде функции, на вход которой передаются свойства и состояние, то результатом функции будет описание пользовательского интерфейса (представление). Свойства и состояния расширяют представления, но они используются для разных целей (см. раздел 4.3).

Работая с состояниями, вы обращаетесь к ним по имени. Имя является атрибутом (то есть ключом объекта или свойством объекта — не свойством компонента) объекта this.state, например this.state.autocompleMatches или this.state.inputFieldValue.

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

Все остальное в DOM остается неизменным. Это возможно благодаря виртуальной модели DOM (см. раздел 1.1.1), которую React использует для определения дельты (совокупности изменений) в процессе согласования. Именно этот факт позволяет писать код в декларативном стиле. React выполняет за вас всю рутинную работу. Основные этапы изменения представления рассматриваются в главе 5.

Разработчики React используют состояния для генерирования новых пользовательских интерфейсов. Свойства компонентов (this.props), обычные переменные (inputValue) и атрибуты классов (this.inputValue) для этого не подойдут, потому что изменение их значений (в контексте текущего компонента) не инициирует изменения представления. Например, следующий фрагмент является антипаттерном, который показывает, что изменение значения в любом месте, кроме состояния, не приведет к обновлению представления:

// Антипаттерн: не делайте так!
let inputValue = 'Texas'
class Autocomplete extends React.Component {
  updateValues() ← { Инициируется в результате действия пользователя (ввод данных)
      this.props.inputValue = 'California'
      inputValue = 'California'
      this.inputValue = 'California'
  }
  render() {
     return (
        <div>
           {this.props.inputValue}
           {inputValue}
           {this.inputValue}
        </div>
     )
  }
}

А теперь посмотрим, как работать с состояниями компонентов React.

Работа с состояниями


Чтобы работать с состояниями, вы должны уметь обращаться к значениям, обновлять их и задавать исходные значения. Начнем с обращения к состояниям в компонентах React.

Обращение к состояниям


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

image Используем то, что вы узнали, для реализации часов на рис. 4.3. Наша цель — создать автономный класс компонента, который любой желающий сможет импортировать и использовать в своем приложении без особых хлопот. На часах должно отображаться текущее время.

Проект имеет следующую структуру:

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

Я использую Babel CLI с флагами отслеживания (-w) и каталога (-d) для компиляции всех исходных файлов 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 и попробуйте организовать рендер состояния так, как показано в листинге 4.1.

Листинг 4.1. Рендер состояния в JSX

class Clock extends React.Component {
   render() {
      return <div>{this.state.currentTime}</div>
   }
}

ReactDOM.render(
   <Clock />,
   document.getElementById('content')
)

Вы получите сообщение об ошибке: Uncaught TypeError: Cannot read property 'currentTime' of null. Обычно от сообщений об ошибках JavaScript пользы примерно столько же, сколько от стакана холодной воды для утопающего. Хорошо, что по крайней мере в этом случае JavaScript выводит осмысленное сообщение.

Из сообщения следует, что значение currentTime не определено. В отличие от свойств, состояния не задаются в родителе. Вызвать setState в render() тоже не получится, потому что это создаст цикл (setState→render→setState…), — и React сообщит об ошибке.

Назначение исходного состояния


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

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

При назначении исходного состояния также можно добавить другую логику — например, задать значение currentTime с использованием new Date(). Вы даже можете использовать toLocaleString() для получения правильного формата даты/времени для текущего местонахождения пользователя, как показано ниже (ch04/clock).

Листинг 4.2. Конструктор компонента Clock

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

Значение this.state должно быть объектом. Мы не будем углубляться в подробности constructor() из ES6; обращайтесь к приложению Д и сводке ES6 по адресу github.com/azat-co/cheatsheets/tree/master/es6. Суть в том, что, как и в других ООП-языках, конструктор (то есть constructor()) вызывается при создании экземпляра класса. Имя метода-конструктора должно быть именно таким; считайте это одним из правил ES6. Кроме того, при создании метода constructor() в него почти всегда должен включаться вызов super(), без которого конструктор родителя не будет выполнен. С другой стороны, если вы не определите метод constructor(), то вызов super() будет предполагаться по умолчанию.

Имя currentTime выбрано произвольно; вы должны использовать это же имя позднее, при чтении и обновлении этого состояния.

Объект state может содержать вложенные объекты или массивы. В следующем примере в состояние добавляется массив с описаниями книг:

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 =… где-то еще, так как это может привести к непредвиденным последствиям.

Так вы получите только исходное значение, которое очень быстро устареет — всего за 1 секунду. Кому нужны часы, которые не показывают текущее время? К счастью, существует механизм обновления текущего состояния.

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


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

Определение обратного вызова callback в setState() важно, потому что метод работает асинхронно. Если работа приложения зависит от нового состояния, вы можете воспользоваться этим обратным вызовом, чтобы убедиться в том, что новое состояние стало доступным.

Если вы просто будете считать, что состояние обновилось, не дожидаясь завершения setState(), то есть работать синхронно при выполнении асинхронной операции, может возникнуть ошибка: работа программы зависит от обновления значений состояния, а состояние остается старым.
До сих пор мы рендерили время из состояния. Вы уже знаете, как задать исходное состояние, но ведь оно должно обновляться каждую секунду, верно? Для этого нужно использовать функцию-таймер браузера setInterval() (http://mng.bz/P2d6), которая будет проводить обновление состояния каждые n миллисекунд. Метод setInterval() реализован практически во всех современных браузерах как глобальный, а это означает, что он может использоваться без каких-либо дополнительных библиотек или префиксов. Пример:

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

Чтобы запустить отсчет времени, необходимо вызвать setInterval() всего один раз. Создадим метод launchClock() исключительно для этой цели; launchClock() будет вызываться в конструкторе. Итоговая версия компонента приведена в листинге 4.3 (ch04/clock/jsx/clock.jsx).

image

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

СОВЕТ Попытка изменения состояния в коде командой вида this.state.name= 'new name' ни к чему не приведет. Она не приведет к повторному рендеру и обновлению реальной модели DOM, чего бы вам хотелось. В большинстве случаев прямое изменение состояния без setState() является антипаттерном, и его следует избегать.

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

constructor(props) {
   super(props)
   this.state = {
      userName: 'Azat Mardan',
      userEmail: 'hi@azat.co',
      userId: 3967
   }
}
updateValues() {
   this.setState({userName: 'Azat'})
}

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

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

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

Волшебство React наступает тогда, когда вы используете данные состояния в представлении (например, при выводе, в команде if/else, как значение атрибута или значение свойства дочернего элемента), а затем передаете setState() новые значения. Бах! React обновляет всю необходимую разметку HTML за вас. В этом можно убедиться в консоли DevTools, где должны отображаться циклы «Updating…» и «Rendering…». А самое замечательное, что это повлияет только на абсолютный минимум необходимых элементов DOM.

» Более подробно с книгой можно ознакомиться на сайте издательства
» Оглавление
» Отрывок

Для Хаброжителей скидка 20% по купону — React

По факту оплаты бумажной версии книги на e-mail высылается электронная версия книги.
Tags:
Hubs:
Total votes 8: ↑8 and ↓0+8
Comments0

Articles

Information

Website
piter.com
Registered
Founded
Employees
201–500 employees
Location
Россия