События и состояния
1. Компоненты-классы
Если необходимо добавить динамику, компоненты создаются как классы.
Обычный ES6 класс, поэтому применяются все правила: конструктор, методы, контекст (this).
Обязательно расширяет базовый класс
React.Component
.Действует как функция, которая получает
props
, но также реализует приватное внутреннее состояние.Необходимо объявить обязательный метод
render()
, который вызывается по умолчанию и возвращает JSX-разметку.Каждый раз при использовании компонента-класса, React будет создавать экземпляр компонента (класса), поэтому доступ к пропсам происходит через
this.props
.Можно определить кастомные методы класса и использовать их в любом месте, в том числе внутри JSX, вызывать или передавать детям как пропсы.
Когда изменяется состояние или пропcы компонента, происходит его ре-рендер.
2. События
Для нативного события браузера в React создается объект-обертка SyntheticEvent Object
с идентичным интерфейсом. Это необходимо чтобы предоставить кросс-бразуерность и оптимизировать производительность.
Добавление обработчика событий с
EventTarget.addEventListener()
почти не используется, за редким исключением.Пропсы событий не исключение и именуются с помощью camelCase. Например
onClick
,onChange
,onSubmit
,onMouseEnter
.В проп события передается ссылка на callback-функцию, которая будет вызвана при наступлении события.
Обработчики событий получают экземпляр
SyntheticEvent Object
.
В React реализовано глобальное делегирование событий. Слушатели не добавляются к DOM-элементам напрямую. React использует один обработчик событий на корне документа, который отвечает за прослушивание всех событий и при необходимости вызывает соответствующий обработчик.
Именно поэтому объект SyntheticEvent
всего один (на все приложение) и доступен только в синхронном коде. Сразу после вызова callback-функции он будет использован повторно и все свойства будут аннулированы.
2.1. Счетчик
Создадим компонент-счетчик с возможностью увеличения и уменьшения значения.
2.2. Анонимные колбеки
Инлайн колбеки считаются антипаттерном. Каждый раз когда компонент ре-рендерится, будет создана новая callback-функция. В многих случаях это нормально. Но, если callback передается как проп нижележащим компонентам в дереве, они будут отрендерены заново, так как придут новые пропы ссылочного типа (функция).
2.3. Кастомные методы
Чаще всего обработчики событий объявляются как методы класса, после чего jsx-атрибуту передается ссылка на метод.
2.4. Привязка контекста
Нужно всегда помнить о значении this
в методах использующихся как callback-функции. В JavaScript, контекст в методах класса не привязывается по умолчанию. Если забыть привязать контекст, и передать метод как callback-функцию обработчику события, во время вызова функции, this
будет неопределен (undefined
).
2.4.1. Привязка при передаче колбека
Избегайте привязки контекста в методе render()
. Всякий раз, когда компонент ре-рендерится, Function.prototype.bind()
возвращает новую функцию и передает ее вниз по дереву компонентов, что приводит к повторному рендеру дочерних компонентов. При достаточном количестве, это оказывает существенное влияние на производительность.
2.4.2. Привязка в конструкторе
Еще один способ привязать контекст - сделать это в конструкторе класса. Если callback-функций много, можете себе представить, насколько большой может получиться конструктор.
Конструктор выполняется один раз, поэтому
bind
вызовется один разМетоды класса записываеются в свойство prototype функции-конструктора
2.4.3. Публичные свойства класса
Несмотря на то, что это рекомендуемый способ привязки контекста, синтаксис публичных полей класса еще не стандартизирован. Но они уже настолько широко используются, что даже если будут синтаксические изменения, транспайлер Babel все сделает за нас.
При объявлении публичных полей класса, они записываются не в свойство prototype
функции-конструктора, а в объект экземпляра.
2.5. Дополнительные материалы
3. Внутреннее состояние компонента
Объект-состояния state
это свойство класса которое не должно изменяться разработчиком напрямую.
Данные в
state
контролируют то, что отображается в интерфейсе.Данные, хранящиеся в состоянии, должны быть информацией, которая будет обновляться методами компонента.
Не нужно дублировать данные из
props
в состоянии.Каждый раз, когда изменяется состояние компонента (или пропсы), вызывается метод
render()
.
В состоянии хранят минимально необходимый набор данных, на основе которых можно вычислить все необходимое для отрисовки интерфейса. Это делается вызовом селекторов (функций которые составляют данные для интерфейса на основе состояния) в методе render()
. Так мы получаем вычисляемые данные.
Интерфейс зависит от состояния компонента.
Состояние может измениться как реакция на действия пользователя.
При изменении состояния, данные передаются вниз по дереву компонентов.
Компоненты возвращают обновленную разметку и изменяется интерфейс.
Состояние принадлежит компоненту и изменяется только его методами. Изменение состояния компонента никогда не повлияет на его родителя, соседей или любой другой компонент в приложении - только на его дочерние элементы. При такой модели, данные в приложении передаются только одним, жестко ограниченным образом. Это называется однонаправленный поток данных.
Состояние объявляется в конструкторе, так как это первое, что происходит, когда создается экземпляр класса.
3.1. Начальное состояние от props
Иногда начальное состояние зависит от переданных пропсов, например начальное значение нашего счетчика. В этом случае, необходимо явно объявить параметр props
в конструкторе и передать его в вызов super(props)
. Тогда в конструкторе будет доступно this.props
.
Так как под капотом используется Babel, можно пропустить утомительное объявление конструктора и указать состояние как публичное свойство класса, все остальное транспайлер сделает за нас.
3.2. Изменение состояния компонента
Для обновления состояния используется встроенный метод setState()
.
Первым, обязательным аргументом, передается объект с полями указывающими какую часть состояния необходимо изменить.
Вторым, необязательным аргументом, можно передать callback-функцию которая выполнится после изменения состояния.
Нельзя изменять состояние напрямую по ссылке. Будьте очень внимательны, особенно при работе со ссылочными типами (массив, объект).
Этот подход используется когда новое состояние не рассчитывается на основе предыдущего. То есть когда в состояние записывается что-то новое, перезаписывая уже существующее. Сделаем компонент с переключателем, методы которого будут перезаписывать значение isOpen
в состоянии.
3.3. Как обновляется состояние
При вызове setState()
не нужно передавать все свойства хранящиеся в состоянии. Достаточно указать только ту часть (срез) состояния, которую мы хотим изменить в данной операции. React затем берет текущее состояние и объект, который был передан в setState()
, объединяя их следующим образом.
3.4. Асинхронность обновления состояния
Метод setState()
регистрирует асинхронную операцию обновления состояния, которая ставится в очередь обновлений. React изменяет состояние не для каждого вызова setState()
, а может объединять несколько вызовов в одно обновление для повышения производительности. Из-за этого доступ к this.state
, в синхронном коде, после вызова этого метода вернет значение до обновления.
Представьте, что при изменении состояния, вы полагаетесь на текущее значение состояния при вычислении следующего. Используем цикл for
для создания (регистрации) нескольких обновлений.
Значение свойства 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 }
.
Поэтому нельзя полагаться на текущее состояние при вычислении следующего, зависящего от предыдущего на момент обновления. Это может привести к ошибкам. Поэтому существует второй способ обновить состояние.
3.5. setState с функцией
Этот подход используется, когда новое значение вычисляется на основе предыдущего состояния. Метод setState()
, первым аргументом, может принимать не объект, а функцию, которая должна возвращать объект которым мы хотим обновить состояние.
Актуальное состояние и пропы, на момент асинхронного исполнения функции переданной в setState()
, будут переданы в нее аргументами state
и props
. Таким образом, можно быть уверенными в корректном значении предыдущего состояния при создании следующего.
Каждый раз, во время вызова функции переданной в setState()
, в параметр prevState
будет передана ссылка на актуальное состояние в момент обновления. Получим объекты обновлений {value: 0 + 1}
, {value: 1 + 1}
, {value: 2 + 1}
, и, в результате, this.state.value
будет содержать 3
.
Теперь можем заменить функционал открыть/закрыть в компоненте <Toggle>
.
А счетчик будет выглядеть так.
3.6. Подъем состояния (state hoisting)
Так как React использует однонаправленный поток данных сверху вниз, для того, чтобы изменить состояние родителя при событии в ребенке, используется следующий паттерн с callback-функцией.
В родителе есть состояние и метод который его изменяет.
Ребенку, в виде пропа, пробрасывается метод родителя изменяющий состояние родителя.
В ребенке происходит вызов переданного ему метода.
При вызове этого метода изменяется состояние родителя.
Происходит ре-рендер поддерева компонентов родителя.
Рассмотрим простой, но наглядный пример.
При клике кнопки, состояние App
обновляется с помощью callback-функции, контекст которой привязан к App
. Этот паттерн устанавливает четкую границу между "умными" и "глупыми" компонентами.
Паттерн подъема состояния может иметь любую вложенность.
4. Типы внутренних данных компонента
static data
- статические свойства и методы к которым необходимо получать доступ без экземпляра.this.state.data
- динамические данные изменяющиеся методами компонента, состояние.this.data
- данные которые будут разные для каждого экземпляра.const DATA
- константы, данные которые не изменяются и одинаковы для всех экземпляров.
Last updated
Was this helpful?