React урок 2 Внутри абстракции DOM

В прошлой уроке мы узнали что React абстрагирует DOM, обеспечивает высокую производительность, а так же возможность создавать итоговый HTML компонентов на сервере и даже создание нативных интерфейсов для мобильных платформ.

Этот урок посвящен JSX расширению языка JS.

События в React

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

HTML предоставляет красивый и легкий для понимания интерфейс обработки событий в виде атрибутов тега: onclick, onfocus и т.д. Но с ними есть проблемы, взять хотя бы проблемы кроссбраузерности, загрязнение глобальной области видимости, утечки памяти и т.д.

JSX используется столь же простой в использовании и понимании API, без нежелательных побочных эффектов. Однако есть некоторые отличия от реализации HTML на пример это касается названий событий, они записываются в верблюжьей нотации («OnClick» в место «onclick»).

Поддерживаемые виды событий:

Touch и Click

onTouchStart onMouseMove onDragEnd
onTouchEnd onMouseEnter onDragOver
onTouchMove onMouseLeave onDrop
onTouchCancel onMouseOut onContextMenu
 onClick onDrag
onDoubleClick onDragEnter
onMouseDown onDragLeave
onMouseUp onDragExit
onMouseOver onDragStart

События клавиатуры

onKeyDown onKeyUp onKeyPress

Фокус и события формы

onFocus onChange
onBlur onInput
onSubmit

Другие события

onScroll onCopy
onWheel onCut
onPaste

Канбан доска: Управление событиями DOM 

В последней итерации приложения канбан, мы добавили стрелочную функцию внутри обработчика OnClick:

В принципе можно так сделать, но это не очень гибкий подход. Давайте изменим эту реализацию на новую, создадим новый метод toggleDetails  внутри класса для обработки события:

Копаем глубже в JSX

JSX в React является расширением JS, в нем используется XML внутри JS кода. Для веб приложений JSX предоставляет набор XML тегов которые очень похожи на HTML теги, но есть и другие случаи, в который другой набор тегов XML используется для описания пользовательского интерфейса, например React SVG, React Canvas и React Native. Когда JSX превращается в обычный JS то XML преобразуется в вызовы соответствующих функций из библиотеки React.

Например из:

в

Использование JSX не является обязательным, но предоставляет следующие преимущества:

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

Различия между JSX и HTML

Есть три важных аспекта при написании HTML в JSX:

  • Атрибуты тегов пишутся в верблюжий нотации;
  • Все элементы должны быть сбалансированы;
  • Имена атрибутов основаны на DOM API, а не на HTML спецификации языка.

Давайте рассмотрим каждый пункт поподробнее.

Атрибуты тегов в верблюжий нотации

Например, при описании HTML тега input можно  добавить атрибут maxlength:

В JSX этот атрибут пишется maxLength (обратите внимание что «L» с большой буквы)

Все элементы должны быть сбалансированы

Если вы знакомы с XML, то наверняка знаете, что каждый элемент у которое нет зарывающего тега должен быть закрыт: «/>». В HTML есть элементы которые противоречат данным правилам, например <br>. Как вы наверняка уже догадались в JSX такие элементы надо обязательно закрывать: <br/>.

Имена атрибутов основаны на DOM API

Это может сбивать с толку, но это на самом деле очень просто. Атрибуты тега могут отличные названия, от привычных атрибутов в HTML. Одним из таких примеров является class и className. Например в HTML:

Если вы захотите изменить имя класса с помощью JS, вы могли бы сделать примерно так:

Как вы заметили в DOM API в замен class используется className. Так как JSX просто расширяет синтаксис JS, атрибуты называются аналогично как определено в DOM API. Например, тот же div может быть реализован на JSX так:

Советы по JSX

JSX может быть иногда сложным. В этом разделе небольшие методы и советы для решения общих проблем с которыми вы можете столкнуться при создании компонентов с JSX.

Один корневой узел

React компоненты могут иметь только один корневой элемент. Для того чтобы понять причины такого ограничения, давайте посмотрим на пример возвращения из функции render:

В итоге это превращается в инструкцию JS:

С другой стороны, следующий код не является верным:

Если вы помните в JS оператор return может вернуть только одно значение. А в примере выше мы пытаем вернуть два значения, что не правильно. Что бы вернуть несколько элементов сразу их можно просто обернуть в одни корневой элемент:

В итоговом JS это будет выглядеть так:

Условия в JSX

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

в итоге мы получим:

Если мы попытаемся написать if в JSX, например так:

то получим примерно следующее:

и получим ошибку при выполнении:

error_if

Какие есть альтернативы?

Несмотря на то, что не возможно использовать «if» внутри JSX, есть альтернативы. Можно например использовать трехкомпонентное (тернарное) выражение.

Например:

В итоге будет создан корректный JS:

Так же это будет работать для условного рендеринга целых узлов:

Перемещение условий

Кроме использования тернарного оператора, есть еще альтернатива в виде перемещения условия за пределы JSX, как это было в сделано для сокрытия и показа деталей Card.

Вместо:

можно сделать так:

React достаточно умен, чтобы правильно обработать пустое значение и даже не будет создан атрибут class если condition === false.

Канбан доска: индикатор открыты или закрыты подробности карты

В первой статье мы использовали технику перемещения условия для переключения деталей карты. Давайте также используем тернарный оператор для добавления индикатора открыты ли подробности или нет.

indicator

Пустое пространство 

HTML браузере между элементами есть пустое пространство, но в JSX если не указать явно, пустого пространства не будет.

в результате

nospace

Что бы добавить пустое пространство можно сделать так:

space

Комментарии в JSX

Еще одна особенность вытекает из того факта, что JSX не HTML является отсутствие поддержки для комментариев HTML (например, <!— Комментарий —>). Хотя традиционные HTML теги комментарии не поддерживаются, так как JSX это JS, то можно использовать регулярные комментарии JS.

Динамический рендеринг HTML

React имеет встроенную защиту от атак XSS, а это значит, что по умолчанию он не позволит динамически генерировать HTML теги. Это все хорошо, но в некоторых случаях вы можете захотеть сгенерировать HMTL на лету. Одним из примеров может быть предоставление данных в формате markdown.

React предоставляет свойство dangerouslySetInnerHTML позволяющее пропустить защиту XSS и сделать что нибудь напрямую.

Канбан доска: Рендеринг Markdown

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

Что бы разметка заработала нам потребуется сторонняя библиотека https://github.com/chjj/marked (npm install —save marked).

Затем давайте ее используем:

И в итоге:

markdown

Как видно HTML теги не создались. а просто выводятся текстом, что нас явно не устраивает. Давайте это исправим:

mark

React без JSX

JSX привносит краткий и знаковый синтаксис похожий на HTML для описания пользовательского интерфейса. Тем не менее, можно использовать чистый JS без JSX.

Вы можете создавать теги или компоненты с помощью функции React.createElement, которая принимает имя тега или компонента, свойство объекта и переменное число необязательных аргументов ребенка.

Фабрика элементов

Для удобства React предоставляет сокращенные функции React.DOM для обычных HTML-тегов. Давай рассмотрим на более сложном примере:

Это эквивалентно следующему:

На JS ES6 можно создать более краткую форму

Стили в JSX

Это может показаться странным, но в React можно писать стили на JS и это дает некоторые преимущества:

  • Стили без заданной области селекторов;
  • Избегает конфликтов спецификации;
  • Полный спектр конструкций, переменные, функции и т.д.

Определение строенных стилей

В React компоненты, встроенные стили можно хранить в переменных JS. Названия свойством должны соответствовать DOM API, например node.style.backgroundImage. Пример:

Канбан доска: Цветная карточка

  1. Добавим цвет в модель данных.

2. Теперь давайте передадим в список

3. Создадим новый div и стиль для него

result_style

Работа с формами

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

Управляемые компоненты

Давайте разберем на примере формы поиска:

В итоге у нас получится поле поиска с неизменяемым значением «React»

search

Это мягко говоря бесполезный поиск. Чтобы поправить данное положение, давайте добавим изменяемое значение.

И добавим событие для обновления состояния:

Это выглядеть немного запутано, но имеет некоторые преимущества:

  • Состояние изменяется через интерфейс и полностью управляется в коде JS;
  • Эта модель позволяет легко реализовать проверку вводимых данных.

Например можно ограничить ввод 50 символами:

Особые случаи

Есть несколько особых случаев: textarea и select

textarea

В HTML содержимое обычно устанавливается между тегами textarea.

Что легко позволяет создавать многострочный текст, однако в React лучше использовать атрибут value и что бы сделать перенос строки достаточно использовать «\n».

select

В HTML, что бы узнать выбранный элемент из списка используется атрибут «selected». Как вы наверно догадались в JSX используется «value»:

Неуправляемые компоненты

Управляемые компоненты придерживаются принципов заложеныx в самом React. В то время как неуправляемые компоненты являются анти-паттерном. Но вам может не требоваться такой жесткий контроль каждого поля на форме, а например только получения всех данных формы после полного заполнения ее пользователем.

Любое поле на форме без атрибута value является неуправляемым компонентом. Например:

Тут мы имеем два пустых input и можем без проблем туда что либо ввести. Если вы хотите установить начальное значение для неуправляемого поля используйте defaultValue взамен value. С помощью события onSubmit мы можем обрабатывать такие формы, например так:

Канбан доска: Форма создания задачи

Давайте добавим неуправляемое текстовое поле для добавления задач.

Так как мы не указали атрибут value пользователь может свободно заполнять это поле. В следующем уроке добавим добавление в список задач. А пока давайте добавим немного СSS:

Виртуальный DOM под капотом

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

  • При сравнении узлов DOM деревьев, если узлы разных типов (например, был div стал span), React будет рассматривать их как два разных субъекта дерева: выбросит первый и построит/вставит второй.
  • Так же логика работает для пользовательских компонентов. Если они не имеют одного и того же типа, React не собирается даже пытаться смотреть что они делают. Просто удалит первый вставить второй.
  • Если узлы имеют одинаковый тип в React имеется два способа обработки на такой случай:
  1. Если это DOM-элемент и например <div id=»before» /> изменится до <div id=»after» /> React изменит только атрибуты и стили без пересоздания элемента в DOM дереве.
  2. Если это пользовательский компонент например изменился <Contact details={false} /> до <Contact details={true} /> React не заменит компонент. Вместо этого он будет передавать новые значения свойств текущему компоненту, этот процесс будет закончен с вызовом функции render данного компонента.

Ключи

Хотя алгоритмы поиска различий виртуального DOM и реального достаточно умны и быстры. React имеет некоторые допущения особенно при построении списков. Давай рассмотрим их на примере списка до и после изменений:

Разница между этими двумя списка кажется очевидной, но что является лучшим подходом к трансформации первого списка во второй? Например тут возможно добавление нового элемента Apple и удаление последнего, но возможно и изменение имени последнего элемента и его положения, а также другие варианты развития событий. Из за того что трудно определить что же произошло и какие компоненты изменились точно, могут появляться побочные эффекты, плюс в больших спискам из-за этого может страдать производительность. Чтобы этого избежать в React ввели понятие ключей, уникальных идентификаторов элементов в списке, что позволяет быстро искать элементы и применять к ним изменения без побочных эффектов.

Канбан доска: Ключи

Если посмотреть в консоль нашего приложение можно увидеть, что React нас уже предупреждает, что мы к сожалению не используем ключи.

console_key

Ключом может быть любое уникальное значение, которое является уникальным и постоянным. В наших данных есть подходящий кандидат — id карточки. Давайте его и используем:

Давайте также добавим ключи для списка задач:

Refs

При работе с React вы всегда имеете дело с виртуальным DOM. Но в некоторых случаях вам может понадобиться «достучаться» до фактической DOM разметки. В React есть такая возможность называемая refs.  Но подумайте дважды перед применением этого метода. Давайте разберем его на примере:

Тут мы пометили некое текстовое поле и теперь мы можем в коде его использовать через свойство this.refs например так:

Походу этих уроков мы редко будем пользоваться этой возможностью. Давайте рассмотрим еще один пример, когда использование refs оправдано:

Тут у нас компонент, который представляет собой текстовое поле и кнопку и по нажатию на которую полю передается фокус.

Итоги

В этом уроке мы узнали много нового о React DOM абстракции. Рассмотрели события, реквизиты, встроенные стили, работу с формами и другие нюансы работы React.