Эта статья — перевод оригинальной статьи Lindsay Wardell "Building Web Components with Vue 3.2"
Также я веду телеграм канал “Frontend по-флотски”, где рассказываю про интересные вещи из мира разработки интерфейсов.
Вступление
Вы когда-нибудь работали над несколькими проектами и хотели иметь набор настраиваемых компонентов, которые можно было бы использовать во всех из них? Будь то работа или просто побочные проекты, набор компонентов, к которым вы можете обратиться, - отличный способ ускорить работу в новом или существующем проекте. Но что, если не все ваши проекты используют одну и ту же структуру пользовательского интерфейса? Или, что, если у вас есть тот, который вообще не использует какой-либо фреймворк JavaScript и полностью отрисован на сервере?
Как разработчик Vue, в идеале мы хотели бы просто использовать наш фреймворк для создания сложных пользовательских интерфейсов. Но иногда мы оказываемся в описанной выше ситуации, работая с другим фреймворком JavaScript, таким как React или Angular, или используя внутреннюю систему рендеринга, такую как Rails или Laravel. Как мы можем создать многоразовый пользовательский интерфейс для различных вариантов внешнего интерфейса?
В Vue 3.2 у нас теперь есть решение этой проблемы: веб-компоненты на базе Vue!
Веб-компоненты
Согласно MDN, «Веб-компоненты - это набор различных технологий, позволяющих вам создавать многоразовые настраиваемые элементы - с их функциональностью, инкапсулированной отдельно от остальной части вашего кода - и использовать их в ваших веб-приложениях». Рассмотрим несколько существующих элементов HTML, например select или video. Эти интерактивные элементы содержат собственный базовый стиль (обычно предоставляемый браузером), некоторую внутреннюю логику и способ прослушивания событий. Веб-компоненты позволяют разработчикам создавать свои собственные элементы и ссылаться на них в своем HTML - никакой инфраструктуры не требуется.
Вот очень простой пример веб-компонента, который будет отображать текущее время.
// Create the Web Component class CurrentTime extends HTMLElement { connectedCallback() { this.innerHTML = new Date(); setInterval(() => this.innerHTML = new Date(), 1000) } } // Define it as a custom element customElements.define('current-time', CurrentTime);
После того, как пользовательский веб-компонент определен, он может отображаться как часть модели DOM, как и любой стандартный элемент HTML. Мы можем использовать этот элемент в нашем HTML так:
<body> <h1>The time is:</h1> <current-time></current-time> </body>
Мы также можем использовать настраиваемые атрибуты с этими элементами, что позволяет нам передавать в них данные (аналогично props во Vue). Обратите внимание, что объекты нельзя передавать как атрибуты, потому что это концепция JavaScript, а не HTML.
<body> <h1>The time is:</h1> <current-time time-zone="America/Los_Angeles"></current-time> </body>
Хотя мы могли бы довольно легко написать эту логику в теге script, используя обычный JavaScript, использование веб-компонентов дает нам возможность инкапсулировать определенную логику и функции внутри компонента, тем самым сохраняя наш код более организованным и понятным. По этой же причине мы используем компонентные фреймворки, такие как Vue и React. Кроме того, как мы обсуждали ранее, веб-компоненты являются гибкими в том смысле, что их можно использовать без JS-фреймворка, но они также совместимы с современными фреймворками (React и Vue и другие поддерживают использование веб-компонентов).
Веб-компоненты на базе Vue
Vue 3.2 включает встроенную поддержку для создания пользовательских веб-компонентов при использовании Vue API. Таким образом, мы получаем лучшее из обоих миров - настраиваемые повторно используемые компоненты во фреймворках / интерфейсах, а также превосходный API Vue. Давайте возьмем наш пример получения текущего времени и переведем его в компонент Vue. Мы будем использовать <script setup>, который сегодня является рекомендуемым способом написания однофайловых компонентов Vue.
Для начала создадим наш новый файл CurrentTime.ce.vue (ce в данном случае означает пользовательский элемент).
<template> {{ currentDateTime }} </template> <script setup> import { ref } from 'vue' let currentDateTime = ref(new Date()) setInterval(() => { currentDateTime.value = new Date() }) </script>
Отлично, наш компонент делает именно то, что мы делали раньше. Затем нам нужно импортировать его где-нибудь в наш основной Javascript и определить его как настраиваемый элемент.
import { defineCustomElement } from 'vue' import CurrentTime from './CurrentTime.ce.vue' const CurrentTimeElement = defineCustomElement(CurrentTime) customElements.define('current-time', CurrentTimeElement)
Что мы здесь сделали?
Сначала мы импортируем функцию
defineCustomElementиз Vue, которая преобразует компонент Vue в настраиваемый элемент.Затем мы импортируем наш Vue SFC и передаем его в
defineCustomElement, создавая конструктор, необходимый для API веб-компонентов.Затем мы определяем настраиваемый элемент в DOM, снабжая его тегом, который мы хотим использовать (current-time), и конструктором, который он должен использовать для рендеринга.
Теперь наш веб-компонент Vue может отображаться в нашем приложении! А поскольку веб-компоненты работают во всех современных фреймворках (а также в фреймворках, отличных от JS, таких как Ruby on Rails или Laravel), теперь мы можем создать набор веб-компонентов для нашего приложения с помощью Vue, а затем использовать их в любом интерфейсе, который мы захотим. .
Вот базовый пример использования ванильного JS и шаблона Vite по умолчанию:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <link rel="icon" type="image/svg+xml" href="favicon.svg" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Vite App</title> </head> <body> <div id="app"> <h1>Hello Vue Web Components!</h1> <current-time></current-time> </div> <script type="module" src="/main.js"></script> </body> </html>
Вы можете увидеть рабочий пример на Stackblitz.
Больше возможностей
Однако создание базового компонента Vue - не всегда то, чем вы хотите заниматься. Что произойдет, если нам понадобится использовать props или события? К счастью, Vue тоже позаботился о нас. Давайте рассмотрим некоторые из основных функций, которые мы ожидаем от наших пользовательских компонентов Vue.
Props
Первое, что мы хотим сделать, это передать свойства нашему веб-компоненту. Используя наш компонент <current-time>, мы хотим иметь возможность устанавливать часовой пояс. В этом случае мы можем использовать props, как обычно для элемента HTML.
В нашем HTML-шаблоне давайте изменим наш код на следующий:
<div id="app"> <h1>Hello Vue Web Components!</h1> <current-time time-zone="America/New_York"></current-time> </div>
Если вы сохраните файл сейчас, вы, вероятно, получите в консоли такую ошибку:
Cannot read properties of undefined (reading 'timeZone')
Это потому, что наш props еще не определен в компоненте. Давай позаботимся об этом сейчас. Вернитесь к своему компоненту Vue и внесите следующие изменения:
<template> <div> {{ displayTime }} </div> </template> <script setup> import { ref, computed } from 'vue'; const props = defineProps({ timeZone: { type: String, default: 'America/Los_Angeles', } }); const currentDateTime = ref(new Date()); const displayTime = computed(() => currentDateTime.value.toLocaleString('en-US', { timeZone: props.timeZone, }) ); setInterval(() => { currentDateTime.value = new Date(); }); </script>
В нашем компоненте Vue мы теперь определяем props (используя Vue 3.2 defineProps, который нам не нужно импортировать), а затем используем свойство timeZone для перевода даты в правильную строку часового пояса. Отлично!
Сохраните свои файлы, и наше приложение должно снова заработать, как и ожидалось, но на этот раз оно будет отображать дату в другом часовом поясе. Не стесняйтесь немного поиграть с ним, пробуя разные часовые пояса.
По умолчанию Vue переводит props в их заданные типы. Поскольку HTML позволяет передавать только строки в качестве атрибутов, Vue выполняет перевод в разные типы за нас.
События
Из документации: «События, генерируемые через this.$emit или setup emit, отправляются как нативные CustomEvents в собственном элементе. Дополнительные аргументы события будут представлены в виде массива в объекте CustomEvent в качестве его свойства details».
Давайте добавим базовую отправку события из нашего компонента, которое будет запускать console.log. В Vue компоненте обновите наш блок скрипта следующим образом:
import { ref, computed, watch } from 'vue'; const props = defineProps({ timeZone: { type: String, default: 'America/Los_Angeles', }, }); const emit = defineEmits(['datechange']); const currentDateTime = ref(new Date()); const displayTime = computed(() => currentDateTime.value.toLocaleString('en-US', { timeZone: props.timeZone, }) ); setInterval(() => { currentDateTime.value = new Date(); emit('datechange', displayTime); }, 1000);
Основное изменение, которое мы здесь вносим, - это добавление defineEmit (также доступно без импорта, аналогично defineProps), чтобы определять, какие события делает этот компонент. Затем мы начинаем использовать это в нашем setInterval, чтобы выдать новую дату как событие.
В нашем main.js мы добавим прослушиватель событий в наш веб-компонент.
document .querySelector('current-time') .addEventListener('datechange', recordTime); function recordTime(event) { console.log(event.detail[0].value); }
Благодаря этому, всякий раз, когда первый компонент <current-time> генерирует событие datechange, мы сможем его прослушать и действовать соответствующим образом. Отлично!
Slots
Слоты работают точно так же как и в обычном Vue приложении, включая именованные слоты. Scoped слоты не поддерживаются, так как они являются расширенной функцией в Vue. Кроме того, при использовании именованных слотов в вашем приложении вам нужно будет использовать нативный синтаксис слотов, а не специальный синтаксис слотов Vue.
Давайте попробуем сейчас. В вашем компоненте Vue измените свой шаблон на следующей:
<div> <slot /> {{ displayTime }} </div>
В этом примере мы используем стандартный слот (мы вернемся к именованным слотам позже). Теперь в вашем HTML добавьте текст между тегами <current-time>:
<current-time> The time is </current-time>
Если вы сохраните и перезагрузите свою страницу, вы должны теперь увидеть, что ваш текст (или любой другой контент, который вы хотите) правильно передается в ваш веб-компонент. Теперь давайте сделаем то же самое, используя именованные слоты. Измените свой компонент Vue, чтобы он имел именованный слот, и затем измените ваш HTML-код следующим образом:
<current-time> <span slot="prefix">The time is</span> </current-time>
Результат должен быть таким же! И теперь мы можем использовать именованные слоты в наших веб-компонентах.
Стили
Одна из замечательных особенностей веб-компонентов заключается в том, что они могут использовать shadow root, инкапсулированную часть модели DOM, которая содержит их собственную информацию о стилях. Эта функция также доступна для наших веб-компонентов Vue. Когда вы называете свой файл расширением .ce.vue, по умолчанию он имеет встроенные стили. Это идеально, если вы хотите использовать свои компоненты в качестве библиотеки в приложении.
Provide/Inject
Provide и Inject также работают в веб-компонентах Vue. Однако следует иметь в виду, что они могут передавать данные только другим веб-компонентам Vue. Из документации, «пользовательский элемент, определенный в Vue, не сможет принимать props, предоставляемые компонентом Vue, не являющимся пользовательским элементом».
Заключение
Vue 3.2 предоставляет возможность писать собственные веб-компоненты с использованием знакомого синтаксиса Vue и гибкости Composition API. Однако имейте в виду, что это не рекомендуемый подход к написанию приложений Vue или разработке приложений в целом. В документации очень много говорится о различиях между веб-компонентами и компонентами, специфичными для Vue, а также о том, почему команда Vue считает, что их подход предпочтительнее для веб-разработки.
Однако веб-компоненты по-прежнему являются прекрасной технологией для создания кросс-фреймворковых приложений. В экосистеме веб-разработки существует множество инструментов, ориентированных исключительно на веб-компоненты, такие как Lit или Ionic. Хотя это может быть не рекомендуемый подход к созданию приложений с помощью Vue, он может обеспечить инкапсулированный способ разработки и функционирования определенных функций в командах или проектах.
Независимо от вашего отношения к веб-компонентам, я настоятельно рекомендую вам ознакомиться с этой новой функцией Vue и поэкспериментировать с ней самостоятельно. Вы даже можете попробовать вставить свой компонент в React или Svelte и посмотреть, насколько легко работать с другими фреймворками JavaScript. Самое главное, помните, что экосистема разработки всегда улучшается и растет, и вы должны быть готовы расти вместе с ней.
StackBlitz Demo
Поиграйтесь с StackBlitz demo и посмотрите мой пример веб-компонента на Vue, используемый в моём побочном проекте. Веселитесь!
