[Перевод] Создание веб-компонентов с помощью Vue 3.2

757adc43a6c24c5bbf1c41b5e3a377b9.png

Эта статья — перевод оригинальной статьи 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 так:


  

The time is:

Мы также можем использовать настраиваемые атрибуты с этими элементами, что позволяет нам передавать в них данные (аналогично props во Vue). Обратите внимание, что объекты нельзя передавать как атрибуты, потому что это концепция JavaScript, а не HTML.


  

The time is:

Хотя мы могли бы довольно легко написать эту логику в теге script, используя обычный JavaScript, использование веб-компонентов дает нам возможность инкапсулировать определенную логику и функции внутри компонента, тем самым сохраняя наш код более организованным и понятным. По этой же причине мы используем компонентные фреймворки, такие как Vue и React. Кроме того, как мы обсуждали ранее, веб-компоненты являются гибкими в том смысле, что их можно использовать без JS-фреймворка, но они также совместимы с современными фреймворками (React и Vue и другие поддерживают использование веб-компонентов).

Веб-компоненты на базе Vue

Vue 3.2 включает встроенную поддержку для создания пользовательских веб-компонентов при использовании Vue API. Таким образом, мы получаем лучшее из обоих миров — настраиваемые повторно используемые компоненты во фреймворках / интерфейсах, а также превосходный API Vue. Давайте возьмем наш пример получения текущего времени и переведем его в компонент Vue. Мы будем использовать

Отлично, наш компонент делает именно то, что мы делали раньше. Затем нам нужно импортировать его где-нибудь в наш основной 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 по умолчанию:



  
    
    
    
    Vite App
  

  
    

Hello Vue Web Components!

Вы можете увидеть рабочий пример на Stackblitz.

Больше возможностей

Однако создание базового компонента Vue — не всегда то, чем вы хотите заниматься. Что произойдет, если нам понадобится использовать props или события? К счастью, Vue тоже позаботился о нас. Давайте рассмотрим некоторые из основных функций, которые мы ожидаем от наших пользовательских компонентов Vue.

Props

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

В нашем HTML-шаблоне давайте изменим наш код на следующий:

Hello Vue Web Components!

Если вы сохраните файл сейчас, вы, вероятно, получите в консоли такую ошибку:

Cannot read properties of undefined (reading 'timeZone')

Это потому, что наш props еще не определен в компоненте. Давай позаботимся об этом сейчас. Вернитесь к своему компоненту Vue и внесите следующие изменения:




В нашем компоненте 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);
}

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

Slots

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

Давайте попробуем сейчас. В вашем компоненте Vue измените свой шаблон на следующей:

{{ displayTime }}

В этом примере мы используем стандартный слот (мы вернемся к именованным слотам позже). Теперь в вашем HTML добавьте текст между тегами :

 The time is 

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


  The time is

Результат должен быть таким же! И теперь мы можем использовать именованные слоты в наших веб-компонентах.

Стили

Одна из замечательных особенностей веб-компонентов заключается в том, что они могут использовать 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, используемый в моём побочном проекте. Веселитесь!

© Habrahabr.ru