Web Components. Основы

Базовые вещи касательно Web Components. Способы создания элементов веб-страницы на основе Web Components. Четыре спецификации Web Components. Фундаментальные аспекты компонентов.

Что из себя представляют Web Components

Рецепт Web Components прост - это три фундаментальных блока: HTML, CSS и JavaScript.

HTML определяет каркас компонента, является разметкой как и для обычной страницы. CSS отвечает за визуальное отображение. JavaScript - это логика компонента, его поведение и функциональность. Спецификации Web Components позволяют нам использовать эти три технологии вместе для построения собственных компонентов. Отсутствие дополнительных требований и инструментов существенно упрощают создание и использование таких компонентов как отдельно, так и внутри уже существующих приложений.

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

А наследование и переиспользование уже существующих компонентов делают их универсальным инструментом для приложений любых масштабов. Но, пожалуй, основным достоинством Web Components остается их простота - разработчикам нет необходимости изучать дополнительные фреймворки и библиотеки, все, что нужно - это HTML, CSS и JavaScript.

Таким образом достигаются следующие цели:

Спецификации Web Components

Web Components состоит из четырех спецификаций:

Каждая из этих спецификаций предоставляет отдельную функциональность для создания и использования компонентов.

Custom Elements

Custom Elements описывает как создавать собственные DOM элементы, добавлять свойства и функции к ним, а также расширять их на основе уже существующих элементов. Для создания пользовательского DOM элемента необходимо использовать классы ES2015. Рассмотрим создание пользовательского элемента на простом примере.

<!DOCTYPE html>
<html>
<head>
  <title>Custom Elements</title>
  <script type="text/javascript">
    class FirstCustomElement extends HTMLElement {
      constructor() {
        super();
        console.log(''constructor'');
      }
    }
  
    window.customElements.define(''first-custom-element'', FirstCustomElement);
  </script>
</head>
<body>
  <first-custom-element></first-custom-element>
</body>
</html

Был создан класс расширяющий нативный HTMLElement. Таким образом мы унаследовали функции самого базового DOM элемента. Внутри класса определен конструктор, который будет вызван при создании элемента. В конструкторе обязательно необходимо вызвать метод super() чтобы выполнился конструктор базового класса. Наконец, вызываем метод window.customElements.define, определенный в спецификации Custom Elements v1. На вход он принимает название тега, который в дальнейшем будет использован на странице и, собственно, класс для создания элемента. Теперь мы можем в теле документа вставить наш элемент, после чего в консоли выведется сообщение из метода конструктора.

Помимо конструктора в Custom Elements присутствует ряд методов, предоставляемых API и описанных в спецификации, как методы жизненного цикла (lifecycle hooks). Они используются для выполнения определенных участков кода в отдельные периоды “жизни” компонента.

Рассмотрим три наиболее употребимых из них. Для этого изменим предыдущий пример следующим образом:

<!DOCTYPE html>
<html>
<head>
    <title>Custom Elements</title>
    <script type="text/javascript">
        class FirstCustomElement extends HTMLElement {
            constructor() {
                super();
                console.log(''constructor'');
            }
            connectedCallback() {
                console.log(''connectedCallback'');
            }
            static get observedAttributes() {
                return [''test'']
            }
            attributeChangedCallback(name, oldValue, newValue) {
                console.log(''attributeChangedCallback '', name, oldValue, newValue);
            }
            disconnectedCallback() {
                console.log(''disconnectedCallback'');
            }
        }
        window.customElements.define(''first-custom-element'', FirstCustomElement);
    </script>
</head>
<body>
    <first-custom-element></first-custom-element>
</body>
</html>

Расширение Custom Elements - это возможность создавать свои элементы, основываясь на уже созданных ранее. Для понимания рассмотрим пример.

<!DOCTYPE html>
<html>
<head>
    <title>Custom Elements</title>
    <script type="text/javascript">
        class HelloCustomElement extends HTMLElement {
            sayHello() {
                console.log(''Hello!'');
            }
        }
        window.customElements.define(''hello-custom-element'', HelloCustomElement);
        class HowAreYouCustomElement extends HelloCustomElement {
            constructor() {
                super();
                this.howAreYou();
            }
            howAreYou() {
                this.sayHello();
                console.log(''How are you!'');
            }
        }
        window.customElements.define(''howareyou-custom-element'', HowAreYouCustomElement);
    </script>
</head>
<body>
    <howareyou-custom-element></howareyou-custom-element>
</body>
</html>

После выполнения в консоли будет выведено:

Hello!
How are you!

Расширение Custom Elements на основе стандартных элементов - еще одна очень важная функция при создании своих элементов. Если необходимо добавить небольшой функционал к стандартному DOM элементу это можно сделать через его расширение. Предположим, что у нас есть input, в котором при его создании должно появиться подготовленное заранее содержимое.

<!DOCTYPE html>
<html>
<head>
    <title>Custom Elements</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/document-register-element/1.5.0/document-register-element.js"></script>
    <script type="text/javascript">
        class HelloInputCustomElement extends HTMLInputElement {
            connectedCallback() {
                this.setAttribute(''value'', ''Hello'');
            }
        }
        window.customElements.define(
          ''helloinput-custom-element''
          HelloInputCustomElement, {
            extends: ''input''
          }
        );
    </script>
</head>
<body>
    <input is="helloinput-custom-element"></input>
</body>
</html>

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

  1. Унаследовались от расширяемого элемента extends HTMLInputElement.

  2. При создании элемента добавили тег расширяемого элемента window.customElements.define(''helloinput-custom-element'', HelloInputCustomElement, {extends: ''input''}).

  3. В документе наш элемент используется через указание атрибута is в стандартном теге.

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

Итак, мы рассмотрели, пожалуй, основную из спецификаций Web Components. Custom Elements является ядром компонента, именно с неё начинается его создание.

Shadow DOM

Следующая из спецификаций Web Components называется Shadow DOM. Она позволяет создавать дополнительное DOM дерево для создаваемого элемента. Особенность такого внутреннего DOM дерева заключается в его инкапсуляции - независимости от основного DOM дерева.

Существует два способа использования Shadow DOM - в закрытом и открытом виде:

Рассмотрим пример кода:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Shadow DOM</title>
<style>
input {
border-color: red;
margin: 10px;
}
</style>
</head>
<body>
<input type="text" />
<div id="hello"></div>
<input type="text" />
<script>
let $helloDiv = document.getElementById(''hello'');
$helloDiv.attachShadow({
mode: "open"
});
$helloDiv.shadowRoot.innerHTML =
`<style>
input {
border: none;
}
</style>
<input type="text" value="hello"/>`
;
</script>
</body>
</html>

В результате его исполнения браузер отобразит три input элемента, два из них с красной рамкой и один без нее:

Такой результат получился в результате использования функции attachShadow и добавления дополнительного содержимого через shadowRoot.innerHTML. Если посмотреть в инспектор кода, то можно увидеть структуру дерева дополнительного DOM.

Дополнительное содержимое было добавлено в shadow-root. Это позволяет компонентам не влиять на общие стили страницы и не испытывать их влияние на себе.

HTML Templates

HTML Templates позволяет создавать шаблоны на основе HTML. Такие шаблоны обладают рядом особенностей:

  1. Создаются с помощью тега .

  2. Не отображаются в браузере до момента их явного использования.

  3. Могут быть использованы для определения разметки компонента и её дальнейшего рендеринга во время создания.

Для использования шаблона достаточно создать разметку внутри тега template:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8" />
    <title>HTML Templates</title>
</head>
<body>
    <template id="task">
        <li>
            <h1>Task name</h1>
            <p>Task body</p>
        </li>
    </template>
    <ui id="todo-list"></ui>
    <script>
        const tasks = [{
                name: "Сделать зарядку",
                body: "Разминка, пробежка, подтягивание на перекладине"
            },
            {
                name: "Купить продукты",
                body: "Хлеб, овощи, чай"
            },
            {
                name: "Подготовить доклад",
                body: "Найти проблему, описать суть, найти решение"
            }
        ];
        const $template = document.getElementById(''task'');
        const $todoList = document.getElementById(''todo-list'');
        tasks.forEach(task => {
            const $clone = document.importNode($template.content, true);
            $clone.querySelector(''h1'').innerText = task.name;
            $clone.querySelector(''p'').innerText = task.body;
            $todoList.appendChild($clone);
        });
    </script>
</body>
</html>

В примере выше у нас есть список задач и шаблон для отображения отдельной задачи. С помощью функции document.importNode мы создаем клон шаблона (глубокое клонирование при помощи второго параметра со значением true). Далее измененный шаблон добавляется к элементу списка задач функцией appendChild. Как видно из примера, шаблон был переиспользован несколько раз внутри цикла.

HTML Templates является наиболее устоявшейся из спецификаций, она реализована во всех современных браузерах.

HTML Imports

Последней из спецификаций Web Components является HTML Imports. Как и следует из её названия она описывает способ импорта html файла. Это можно сравнить с импортом css файла внутри страницы. Только в данном случае вместо разметки стилей будет загружен файл с одним или более компонентами.

Для демонстрации создадим два файла. Первый hello.html с содержимым:

<!DOCTYPE html>
<html>
<body>
    <h1>Hello Web Components!</h1>
</body>
</html>

Второй index.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <title>HTML Imports</title>
    <link rel="import" href="hello.html" id="hello" />
</head>
<body>
    <script>
        const $link = document.getElementById("hello");
        const content = $link.import.querySelector("h1");
        document.body.appendChild(content.cloneNode(true));
    </script>
</body>
</html>

Открытие страницы index выведет заголовок из файла hello.html.

HTML Imports является наиболее спорной из всех спецификаций, на сегодняшний день она реализована только в браузере Chrome и Opera. К тому же, смысл работы данной спецификации выглядит несколько избыточным с появлением динамических импортов в ES2015. Тем не менее, функция загрузки компонентов через импорт html файла существует и может оказаться весьма нужной в отдельных случаях.

Итог

На этом мы закончили рассмотрение основ Web Components. Познакомились с целями, которые позволяют достичь компоненты и как эти цели коррелируют с четырьмя спецификациями:

Ссылки