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

Если вы пропустили предыдущие главы, вы можете найти их здесь:

Обзор

В одном из наших предыдущих постов мы обсуждали Shadow DOM API и несколько других концепций, которые являются частями более широкой картины - веб-компонентов. Вся идея, лежащая в основе стандарта веб-компонентов, состоит в том, чтобы иметь возможность расширять встроенные возможности HTML за счет создания небольших модульных элементов, которые можно использовать повторно. Это относительно новый стандарт W3C, который уже одобрен всеми основными браузерами и его можно увидеть в производственной среде… конечно, с помощью библиотеки полифиллов (о которой мы поговорим позже в этой статье).

Как вы, возможно, уже знаете, браузер предоставляет нам несколько очень важных инструментов для создания веб-сайтов и веб-приложений. Мы говорим о HTML, CSS и JavaScript. Вы используете HTML для структурирования своего приложения, CSS, чтобы оно выглядело красиво, и JavaScript для выполнения действий. Однако до появления веб-компонентов не было простого способа связать поведение JavaScript со структурой HTML.

В этом посте мы поговорим об основах веб-компонентов - пользовательских элементах. Вкратце, API пользовательских элементов позволяет создавать (как следует из названия) пользовательские элементы HTML со встроенной логикой JavaScript и стилями CSS. Многие путают пользовательские элементы с теневым DOM. Но это две совершенно разные концепции, и на самом деле они не взаимозаменяемы, а дополняют друг друга.

Некоторые фреймворки (например, Angular, React) пытаются решить ту же проблему, вводя свои собственные концепции. Вы можете сравнить настраиваемые элементы с директивами Angular или компонентами React. Однако пользовательские элементы встроены в браузер и не требуют ничего, кроме стандартного JavaScript, HTML и CSS. Конечно, это не обязательно означает, что это замена типичного фреймворка JavaScript. Современные фреймворки дают нам больше, чем просто возможность имитировать поведение настраиваемых элементов. Таким образом, они могут работать бок о бок.

API

Прежде чем мы углубимся, давайте кратко рассмотрим, как на самом деле выглядит API. Глобальный объект customElements предоставляет вам несколько методов:

  • define(tagName, constructor, options) - определяет новый настраиваемый элемент. Принимает три аргумента: допустимое имя тега для настраиваемого элемента, определение класса для настраиваемого элемента и объект параметров. В настоящее время поддерживается только одна опция: extends, которая представляет собой строку, определяющую имя расширяемого встроенного элемента. Используется для создания настраиваемых встроенных элементов.
  • get(tagName) - возвращает конструктор настраиваемого элемента, если элемент определен, и возвращает undefined в противном случае. Принимает единственный аргумент: допустимое имя тега для настраиваемого элемента.
  • whenDefined(tagName) - возвращает обещание, которое выполняется после определения настраиваемого элемента. Если элемент уже определен, он будет немедленно разрешен. Обещание отклоняется, если имя тега не является допустимым именем настраиваемого элемента. Принимает единственный аргумент: допустимое имя тега для настраиваемого элемента.

Как создать собственный элемент

На самом деле создание нестандартного элемента - это несложно. Вам нужно сделать две вещи: создать определение класса для элемента, который должен расширять класс HTMLElement, и зарегистрировать этот элемент под выбранным вами именем.

Или, если хотите, вы можете использовать анонимный класс, если не хотите загромождать текущую область видимости.

Как видно из примеров, пользовательские элементы регистрируются с помощью метода customElements.define(...).

Какие проблемы решают пользовательские элементы?

Так в чем собственно проблема. Супы Div являются его частью. Вы можете спросить, что такое суп div - это очень распространенная структура в современных веб-приложениях, где у вас есть несколько вложенных элементов div (div внутри div внутри div и т. Д.).

Такая структура используется, поскольку она заставляет браузер отображать страницу должным образом. Однако это делает HTML нечитаемым и очень сложным в обслуживании.

Так, например, у нас может быть компонент, который должен выглядеть так:

И традиционно HTML может выглядеть следующим образом.

Но представьте, если бы мы могли сделать так, чтобы вместо этого

Второй пример намного лучше, если вы спросите меня. Он более удобен в обслуживании, удобочитаем и имеет смысл как для браузера, так и для разработчика. Это проще простого.

Другая проблема - возможность повторного использования. Наша работа как разработчиков требует не только написания рабочего, но и поддерживаемого кода. И одна вещь, которая делает некоторый код поддерживаемым, - это возможность легко повторно использовать фрагмент кода вместо того, чтобы писать его снова и снова.

Приведу простой пример, но вы уловите суть. Допустим, у нас есть следующий элемент:

Если нам понадобится использовать это где-нибудь еще, нам придется заново писать тот же HTML-код. Теперь представьте, что нам нужно внести изменение, которое необходимо применить к каждому из этих элементов. Нам нужно будет найти каждое место в коде и снова и снова делать одно и то же изменение. Облом…

Не было бы лучше, если бы мы могли просто сделать следующее

Но современное веб-приложение - это не просто статический HTML. Вам нужно взаимодействовать с ним. И это происходит из JavaScript. Обычно вы можете создать некоторые элементы, а затем присоединить любые прослушиватели событий, которые вам нужны, чтобы они реагировали на ввод пользователя. Нажимали ли они, перетаскивали, зависали и т. Д.

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

На первый взгляд может показаться, что для подхода с настраиваемыми элементами требуется больше строк JavaScript. Но в реальном приложении редко бывает такой сценарий, когда вы создаете единственный элемент, который не используется повторно. Еще одна черта, характерная для современных веб-приложений, заключается в том, что большинство элементов создаются динамически. Таким образом, вам нужно будет обрабатывать отдельные случаи, когда элемент добавляется динамически с помощью JavaScript или определен ранее в структуре HTML. И все это вы получаете из коробки с настраиваемыми элементами.

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

Требования

Теперь, прежде чем вы продолжите и создадите свой первый настраиваемый элемент, вы должны знать, что есть особые правила, которые необходимо соблюдать.

  • Имя должно содержать тире (-). Таким образом, анализатор HTML может определить, какие элементы являются настраиваемыми, а какие встроенными. Это также гарантирует, что не будет конфликтов имен со встроенными элементами (сейчас или в будущем, когда будут добавлены другие). Например, <my-custom-element> - допустимое имя, а <myCustomElement> и <my_custom_element> - нет.
  • Запрещается регистрировать одно и то же имя тега более одного раза. Это приведет к тому, что браузер выдаст DOMException. Вы не можете переопределить пользовательские элементы.
  • Пользовательские элементы не могут быть самозакрывающимися. Парсер HTML позволяет только нескольким встроенным элементам быть самозакрывающимися (например, <img>, <link>, <br>).

Возможности

Итак, что на самом деле можно делать с пользовательскими элементами? И ответ - многое.

Одна из лучших особенностей заключается в том, что определение класса элемента фактически относится к самому элементу DOM. Это означает, что вы можете использовать this напрямую для присоединения слушателей событий, доступа к его свойствам, доступа к дочерним узлам и т. Д.

Это, конечно, дает вам возможность перезаписывать дочерние узлы элемента новым содержимым. Но обычно это не рекомендуется, так как это может привести к неожиданному поведению. Как пользователь настраиваемого элемента, который не реализован вами, вы будете удивлены, если ваша собственная разметка внутри элемента будет заменена чем-то другим.

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

constructor

Конструктор вызывается после создания или обновления элемента (мы поговорим об этом чуть позже). Чаще всего он используется для инициализации состояния, присоединения слушателей событий, создания теневой модели DOM и т. Д. Следует помнить, что всегда следует вызывать super() в конструкторе.

connectedCallback

Метод connectedCallback вызывается каждый раз, когда элемент добавляется в DOM. Его можно использовать (также рекомендуется), чтобы отложить некоторую работу до тех пор, пока элемент не появится на странице (например, выбор ресурса).

disconnectedCallback

Аналогично connectedCallback, метод disconnectedCallback вызывается после того, как элемент извлекается из DOM. Обычно используется для освобождения ресурсов. Следует иметь в виду, что disconnectedCallback никогда не вызывается, если пользователь закрывает вкладку. Так что будьте осторожны с тем, что вы инициализируете в первую очередь.

attributeChangedCallback

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

addoptedCallback

Метод addoptedCallback вызывается после вызова метода document.adoptNode(...), чтобы переместить его в другой документ.

Обратите внимание, что все вышеперечисленные обратные вызовы синхронны. Например, подключенный обратный вызов вызывается сразу после добавления элемента в DOM, и в это время больше ничего не происходит.

Отражение собственности

Встроенные элементы HTML предоставляют одну очень удобную возможность: отражение свойств. Это означает, что значения некоторых свойств напрямую отражаются обратно в DOM как атрибут. Таким примером является свойство id.

myDiv.id = 'new-id';

Также обновит DOM до

<div id="new-id"> ... </div>

И это применимо и в обратном направлении. Это очень полезно, поскольку позволяет декларативно настраивать элементы.

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

Расширяющиеся элементы

API пользовательских элементов позволяет не только создавать новые элементы HTML, но и расширять существующие. И он отлично работает как со встроенными элементами, так и с другими настраиваемыми элементами. И это достигается путем расширения определения класса.

Или, в случае встроенных элементов, нам нужно также добавить третий параметр к функции customElements.define(...), которая является объектом со свойством extends и значением имени тега расширяемого элемента. Это сообщает браузеру, какой именно элемент расширяется, поскольку многие встроенные элементы используют один и тот же интерфейс DOM. Если не указать, какой именно элемент вы расширяете, браузер не узнает, какие функции расширяются.

Расширенный собственный элемент также называется настраиваемым встроенным элементом.

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

Обратите внимание, что настраиваемые встроенные элементы сейчас поддерживаются только Chrome 67+. Он будет реализован и в других браузерах, но Safari решил не реализовывать его вообще.

Обновление элементов

Как упоминалось выше, мы используем метод customElements.define(...) для регистрации настраиваемого элемента. Но это не значит, что это первое, что вам нужно сделать. Регистрацию пользовательского элемента можно отложить на некоторое время в будущем. Даже после того, как сам элемент добавлен в DOM. Этот процесс называется обновлением элемента. Чтобы вы знали, когда элемент фактически определен, браузер предоставляет вам метод customElements.whenDefined(...). Вы передаете ему имя тега элемента, он возвращает обещание, которое разрешается после регистрации элемента.

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

Теневой DOM

Как мы уже говорили, пользовательские элементы и теневой DOM должны быть вместе. Первый используется для инкапсуляции логики JavaScript в элемент, а второй - для создания изолированной среды для части DOM, на которую не влияет внешний мир. Я предлагаю вам ознакомиться с одним из наших предыдущих сообщений в блоге, посвященным теневой модели DOM, чтобы лучше понять эту концепцию.

А чтобы использовать теневую модель DOM для пользовательского элемента, вам просто нужно вызвать this.attachShadow

Шаблоны

Мы кратко говорили о шаблонах в одной из наших предыдущих публикаций, и только они заслуживают отдельного поста. Здесь мы собираемся привести простой пример того, как вы можете включить шаблоны в создание ваших пользовательских элементов. Используя <template>, вы можете объявить тег фрагмента DOM, который анализируется, но не отображается на странице.

Итак, теперь, когда мы объединили пользовательские элементы с теневой DOM и шаблонами, мы получили элемент, который изолирован в своей собственной области видимости и имеет хорошее разделение структуры HTML и логики JavaScript.

Стиль

Итак, мы прошли через HTML и JavaScript, но как насчет CSS. Очевидно, нам нужен способ стилизовать наши элементы. Мы можем добавить таблицы стилей CSS внутри теневой модели DOM, но тогда вы можете спросить, как мы стилизуем элементы снаружи как пользователи элемента. И ответ прост - вы стилизуете их так же, как и встроенные элементы.

Обратите внимание, что стиль, определенный извне, имеет более высокий приоритет и переопределит стиль, определенный из элемента.

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

Неизвестные элементы против неопределенных пользовательских элементов

Спецификация HTML очень гибкая и позволяет объявлять любой тег, который вы хотите. И если тег не распознается браузером, он будет проанализирован как HTMLUnknownElement.

Однако это не относится к настраиваемым элементам. Помните, когда мы говорили, что существуют особые правила именования для определения пользовательских элементов? Причина в том, что если браузер видит допустимое имя для настраиваемого элемента, он анализирует его как HTMLElement и рассматривает браузер как неопределенный настраиваемый элемент.

Хотя визуальных различий между HTMLElement и HTMLUnknownElement может и не быть, следует помнить о других вещах. Синтаксический анализатор обрабатывает их по-разному. Ожидается, что элемент с допустимым именем настраиваемого элемента будет иметь настраиваемую реализацию. И пока эта реализация не определена, она рассматривается как пустой элемент div. В то время как неопределенный элемент не реализует какой-либо метод или свойство какого-либо встроенного элемента.

Поддержка браузера

Первая версия пользовательских элементов была представлена ​​в Chrome 36+. Это был так называемый API пользовательских компонентов v0, который теперь устарел и считается плохой практикой, хотя все еще доступен. Хотя, если вы хотите узнать больше о v0, вы можете прочитать об этом в этом блоге. API пользовательских элементов v1 доступен начиная с Chrome 54 и Safari 10.1 (хотя и частично). Microsoft Edge находится на стадии прототипирования, а в Mozilla он есть с версии 50, но он недоступен по умолчанию и должен быть включен явно. На данный момент полностью его поддерживают только браузеры webkit. Однако, как упоминалось выше, есть полифил, который позволяет использовать пользовательские элементы во всех браузерах. Да даже IE 11.

Проверка доступности

Чтобы убедиться, что браузер поддерживает настраиваемые элементы, вы можете просто проверить, существует ли свойство customElements в объекте window.

Или, если вы используете библиотеку полифиллов:

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

  • Связывание поведения JavaScript и стиля CSS с элементом HTML
  • Позволяет расширять уже существующие HTML-элементы (как встроенные, так и другие настраиваемые)
  • Для начала не требует библиотеки или фреймворка. Вам просто нужен ванильный JavaScript, HTML и CSS и, возможно, библиотека polyfill для поддержки старых браузеров.
  • Он создан для безупречной работы с другими функциями веб-компонентов (теневой DOM, шаблонами, слотами и т. Д.)
  • Тесно интегрирован с инструментами разработчика браузера.
  • Воспользуйтесь существующими функциями универсального доступа.

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

SessionStack интегрируется в веб-приложения для сбора данных, таких как пользовательские события, сетевые данные, исключения, отладочные сообщения, изменения DOM и т. Д., И для отправки этих данных на наши серверы.

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

Таким образом, чтобы гарантировать, что SessionStack всегда будет воспроизводить идеальное по пикселям воспроизведение сеанса, нам необходимо идти в ногу с возникающими технологиями, фреймворками и веб-стандартами.

Есть бесплатный план, если вы хотите попробовать SessionStack.

Использованная литература: