Skip to content

Vue і вебкомпоненти

Вебкомпоненти — це загальний термін для набору власних веб-інтерфейсів API, які дозволяють розробникам створювати багаторазові спеціальні елементи.

Ми вважаємо Vue і вебкомпоненти насамперед додатковими технологіями. Vue має відмінну підтримку як для споживання, так і для створення власних елементів. Якщо ви інтегруєте спеціальні елементи в існуючий додаток Vue або використовуєте Vue для створення та розповсюдження спеціальних елементів, ви в хорошій компанії.

Використання спеціальних елементів у Vue

Vue отримав ідеальні 100% у тестах Custom Elements Everywhere. Використання користувацьких елементів у додатку на Vue здебільшого працює так само, як і використання власних елементів HTML, з деякими речами, про які слід пам’ятати:

Запобігання визначення компонента

За промовчанням Vue намагатиметься розпізнати нерідний HTML-тег як зареєстрований компонент Vue, перш ніж повернутися до відтворення його як спеціального елемента. Це призведе до того, що під час розробки Vue видаватиме попередження «не вдалося визначити компонент» (англ.: "failed to resolve component"). Щоб повідомити Vue, що певні елементи слід розглядати як спеціальні елементи та запобігати їх визначенню як компонентів, ми можемо вказати параметр compilerOptions.isCustomElement.

Якщо ви використовуєте Vue із налаштуваннями збірки, цей параметр слід передати через конфігурації збірки, оскільки це є параметром часу компіляції.

Приклад конфігурації в браузері

js
// Працює, лише якщо використовується компіляція у браузері.
// Якщо ви використовуєте інструменти збирання, перегляньте приклади конфігурації нижче.
app.config.compilerOptions.isCustomElement = (tag) => tag.includes('-')

Приклад конфігурації Vite

js
// vite.config.js
import vue from '@vitejs/plugin-vue'

export default {
  plugins: [
    vue({
      template: {
        compilerOptions: {
          // обробляти всі теги з тире як власні елементи
          isCustomElement: (tag) => tag.includes('-')
        }
      }
    })
  ]
}

Приклад конфігурації Vue CLI

js
// vue.config.js
module.exports = {
  chainWebpack: config => {
    config.module
      .rule('vue')
      .use('vue-loader')
      .tap(options => ({
        ...options,
        compilerOptions: {
          // обробляти будь-які теги, які починаються з ion- як спеціальні елементи
          isCustomElement: tag => tag.startsWith('ion-')
        }
      }))
  }
}

Передача властивостей DOM

Оскільки атрибути DOM можуть бути лише рядками, нам потрібно передавати складні дані користувацьким елементам як властивості DOM. Під час встановлення реквізитів для спеціального елемента Vue 3 автоматично перевіряє наявність властивості DOM за допомогою оператора in і віддасть перевагу встановленню значення як властивості DOM, якщо ключ присутній. Це означає, що в більшості випадків вам не потрібно думати про це, якщо спеціальний елемент відповідає рекомендованим найкращим практикам.

Однак, можуть бути рідкісні випадки, коли дані потрібно передати як властивість DOM, але налаштовуваний елемент не визначає/відображає властивість належним чином (що призводить до помилки перевірки in). У цьому випадку ви можете примусово встановити прив’язку v-bind як властивість DOM за допомогою модифікатора .prop:

template
<my-element :user.prop="{ name: 'jack' }"></my-element>

<!-- скорочений еквівалент -->
<my-element .user="{ name: 'jack' }"></my-element>

Створення користувацьких елементів за допомогою Vue

Основна перевага користувацьких елементів полягає в тому, що їх можна використовувати з будь-якою структурою або навіть без неї. Це робить їх ідеальними для розповсюдження компонентів, де кінцевий споживач може не використовувати той самий фронденд стек, або коли ви хочете ізолювати кінцеву програму від деталей реалізації компонентів, які вона використовує.

defineCustomElement

Vue підтримує створення користувацьких елементів за допомогою тих самих API компонентів Vue за допомогою методу defineCustomElement. Метод приймає той самий аргумент, що й defineComponent, але натомість повертає власний конструктор елемента, який розширює HTMLElement:

template
<my-vue-element></my-vue-element>
js
import { defineCustomElement } from 'vue'

const MyVueElement = defineCustomElement({
  // звичайні параметри компонента Vue тут
  props: {},
  emits: {},
  template: `...`,

  // Лише defineCustomElement: CSS буде введено в тіньовий кореневий елемент
  styles: [`/* inlined css */`]
})

// Зареєструйте користувацький елемент.
// Після реєстрації всі теги `<my-vue-element>`
// на сторінці буде оновлено.
customElements.define('my-vue-element', MyVueElement)

// Ви також можете програмно створити екземпляр елемента:
// (можливо лише після реєстрації)
document.body.appendChild(
  new MyVueElement({
    // початкові реквізити (необов'язково)
  })
)

Життєвий цикл

  • Спеціальний елемент Vue змонтує внутрішній екземпляр компонента Vue у своєму тіньовому корені, коли елемент connectedCallback викликаний вперше.

  • Коли викликається disconnectedCallback елемента, Vue перевірить, чи елемент від’єднано від документа після тіку мікрозавдання.

    • Якщо елемент все ще знаходиться в документі — це переміщення, тому екземпляр компонента буде збережено;

    • Якщо елемент від’єднано від документа — це видалення, тому екземпляр компонента буде демонтовано.

Реквізити

  • Усі реквізити, оголошені за допомогою параметра props, будуть визначені в користувацькому елементі як властивості. Vue автоматично оброблятиме відображення між атрибутами / властивостями, де це необхідно.

    • Атрибути завжди відображаються у відповідних властивостях.

    • Реквізити з примітивними значеннями (string, boolean або number)відображаються як атрибути.

  • Vue також автоматично перетворює властивості, оголошені за допомогою типів Boolean або Number, у потрібний тип, коли вони встановлені як атрибути (які завжди є рядками). Наприклад, задано наступне оголошення реквізитів:

    js
    props: {
      selected: Boolean,
      index: Number
    }

    І використання користувацького елемента:

    template
    <my-element selected index="1"></my-element>

    У компоненті selected буде приведено до true (логічне значення), а index — до 1 (число).

Події

Події, створені через this.$emit або emit в setup, надсилаються як рідні CustomEvents на користувацькому елементі. Додаткові аргументи події (корисне навантаження) відображатимуться як масив об’єкта CustomEvent як його властивість detail.

Слоти

Усередині компонента слоти можна відобразити за допомогою елемента <slot/>, як зазвичай. Однак під час використання отриманого елемента він приймає лише рідний синтаксис слотів:

  • Обмежені слоти не підтримуються.

  • При передачі іменованих слотів використовуйте атрибут slot замість директиви v-slot:

    template
    <my-element>
      <div slot="named">привіт</div>
    </my-element>

Надавання / введення

API надавання / введення і еквівалент композиційного АРІ також працюють з користувацькими елементи, визначеними Vue. Однак зауважте, що це працює тільки з користувацькими елементами. Тобто користувацький елемент, визначений Vue, не зможе вводити властивості, надані компонентом Vue, що не є користувацьким елементом.

SFC як спеціальний елемент

defineCustomElement також працює з однофайловими компонентами Vue (SFC). Однак із налаштуванням інструментів за промовчанням <style> всередині SFC все одно буде витягнуто та об’єднано в один файл CSS під час виробничої збірки. У разі використання SFC як користувацького елемента, часто бажано замість цього додати теги <style> до тіньового кореня користувацького елемента.

Офіційні інструменти SFC підтримують імпорт SFC у «режимі користувацьких елементів» (потрібно @vitejs/plugin-vue@^1.4.0 або vue-loader@^16.5.0). SFC, завантажений у режимі користувацького елемента, вставляє свої теги <style> як рядки CSS і розкриває їх у параметрі styles компонента. Це буде підібрано defineCustomElement і вставлено в тіньовий корінь елемента під час створення екземпляра.

Щоб увімкнути цей режим, просто завершіть назву файлу компонента .ce.vue:

js
import { defineCustomElement } from 'vue'
import Example from './Example.ce.vue'

console.log(Example.styles) // ["/* вбудований css */"]

// перетворити на користувацький конструктор елементів
const ExampleElement = defineCustomElement(Example)

// зареєструйте
customElements.define('my-example', ExampleElement)

Якщо ви бажаєте налаштувати, які файли слід імпортувати в режимі користувацьких елементів (наприклад, розглядаючи всі SFC як користувацькі елементи), ви можете передати опцію customElement до відповідних плагінів збірки:

Поради щодо бібліотеки користувацьких елементів Vue

Під час створення користувацьких елементів за допомогою Vue, елементи покладатимуться на середовище виконання Vue. Існує вартість базового розміру ~16 Кб залежно від того, скільки функцій використовується. Це означає, що Vue не ідеально використовувати, якщо ви надсилаєте окремий користувацький елемент – ви можете використовувати ванільний JavaScript, petite-vue або фреймворки, які спеціалізуються на невеликому розмірі часу виконання. Однак базовий розмір більш ніж виправданий, якщо ви постачаєте колекцію користувацьких елементів зі складною логікою, оскільки Vue дозволить створювати кожен компонент із набагато меншим кодом. Чим більше елементів ви доставляєте разом, тим кращий компроміс.

Якщо користувацькі елементи застосовуються в програмі, яка також використовує Vue, ви можете вибрати екстерналізацію Vue із вбудованого набору, щоб елементи використовували ту саму копію Vue із головної програми.

Рекомендується експортувати окремі конструктори елементів, щоб ваші користувачі мали можливість імпортувати їх на вимогу та реєструвати з бажаними іменами тегів. Ви також можете експортувати зручну функцію для автоматичної реєстрації всіх елементів. Ось приклад точки входу бібліотеки власних елементів Vue:

js
import { defineCustomElement } from 'vue'
import Foo from './MyFoo.ce.vue'
import Bar from './MyBar.ce.vue'

const MyFoo = defineCustomElement(Foo)
const MyBar = defineCustomElement(Bar)

// експорт окремих елементів
export { MyFoo, MyBar }

export function register() {
  customElements.define('my-foo', MyFoo)
  customElements.define('my-bar', MyBar)
}

Якщо у вас багато компонентів, ви також можете скористатися функціями інструментів для створення, такими як glob import Vite або require.context, щоб завантажити всі компоненти з каталогу.

Веб-компоненти та Typescript

Якщо ви розробляєте програму чи бібліотеку, ви можете перевірити тип свої компоненти Vue, включно з тими, які визначені як спеціальні елементи.

Спеціальні елементи реєструються глобально за допомогою власних API, тому за замовчуванням вони не матимуть визначення типу під час використання в шаблонах Vue. Щоб забезпечити підтримку типів для компонентів Vue, зареєстрованих як спеціальні елементи, ми можемо зареєструвати типи глобальних компонентів за допомогою [GlobalComponents інтерфейсу](https://github.com/vuejs/language-tools/blob/master/packages/vscode- vue/README.md#usage) у шаблонах Vue та/або в JSX:

typescript
import { defineCustomElement } from 'vue'

// vue SFC
import CounterSFC from './src/components/counter.ce.vue'

// перетворити компонент на веб-компоненти
export const Counter = defineCustomElement(CounterSFC)

// реєструємо глобальні типи
declare module 'vue' {
  export interface GlobalComponents {
    'Counter': typeof Counter,
  }
}

Веб-компоненти проти компонентів Vue

Деякі розробники вважають, що слід уникати власних моделей компонентів фреймворку, і що виняткове використання спеціальних елементів робить програму «придатною для майбутнього». Тут ми спробуємо пояснити, чому ми вважаємо, що це надто спрощений погляд на проблему.

Між спеціальними елементами та компонентами Vue справді існує певний рівень перекриття функцій: обидва вони дозволяють нам визначати багаторазово використовувані компоненти з передачею даних, створенням подій та керуванням життєвим циклом. Однак API вебкомпонентів є відносно низькорівневими та базовими. Щоб створити справжню програму, нам потрібно чимало додаткових можливостей, які платформа не охоплює:

  • Декларативна та ефективна система шаблонів;

  • Реактивна система управління станом, яка полегшує міжкомпонентне вилучення та повторне використання логіки;

  • Ефективний спосіб візуалізації компонентів на сервері та гідратації їх на клієнті (SSR), що важливо для SEO та показників Web Vitals, таких як LCP. Рідні користувацькі елементи SSR зазвичай включають симуляцію DOM у Node.js, а потім серіалізацію мутованого DOM, тоді як Vue SSR компілює в конкатенацію рядків, коли це можливо, що набагато ефективніше.

Модель компонентів Vue розроблена з урахуванням цих потреб як узгоджена система.

Маючи компетентну команду інженерів, ви, ймовірно, зможете створити еквівалент на основі власних спеціальних елементів, але це також означає, що ви берете на себе довготерміновий тягар обслуговування внутрішньої структури, водночас втрачаючи переваги екосистеми та спільноти зрілий фреймворк, такий як Vue.

Існують також фреймворки, побудовані з використанням спеціальних елементів як основи їх компонентної моделі, але всі вони неминуче мають запроваджувати власні рішення проблем, перелічених вище. Використання цих фреймворків передбачає прийняття їхніх технічних рішень щодо вирішення цих проблем — що, незважаючи на те, що може бути рекламоване, автоматично не захищає вас від потенційних майбутніх відтоків.

Є також деякі області, де ми вважаємо, що спеціальні елементи є обмеженнями:

  • Ретельне оцінювання слотів перешкоджає створенню компонентів. Обмежені слоти Vue — це потужний механізм композиції компонентів, який не підтримується користувацькими елементами через потребу рідних слотів. Охочі слоти також означають, що приймаючий компонент не може контролювати, коли та чи відтворювати частину вмісту слота.

  • Для доставки користувацьких елементів із CSS із тіньовою областю DOM сьогодні потрібно вбудовувати CSS у JavaScript, щоб їх можна було вставляти в тіньові корені під час виконання. Вони також призводять до дублювання стилів у розмітці в сценаріях SSR. У цій сфері працюють над функціями платформи, але наразі вони ще не підтримуються всюди, і все ще є проблеми з виробничою продуктивністю / SSR, які необхідно вирішити. Тим часом Vue SFC надає механізми визначення області CSS, які підтримують вилучення стилів у звичайні файли CSS.

Vue завжди буде в курсі найновіших стандартів вебплатформи, і ми з радістю використаємо все, що пропонує платформа, якщо це полегшить нашу роботу. Проте наша мета — надавати рішення, які добре працюють і працюють сьогодні. Це означає, що ми маємо впроваджувати нові функції платформи з критичним мисленням — і це передбачає заповнення прогалин, де стандарти не відповідають вимогам.

Vue і вебкомпоненти has loaded