2024-11-09|閱讀時間 ‧ 約 0 分鐘

EP63 - Vue與Web組件

Vue and Web Components,其實不太懂什麼web組件
快來看看是什麼東西吧 www ~~~

Web Components 是一組網頁原生 API 的統稱,它允許開發者創建可重複使用的自訂元素。

我們認為 Vue 和 Web Components 是主要互補的技術。Vue 對於使用和創建自訂元素都有出色的支援。無論你是將自訂元素整合到現有的 Vue 應用程式中,還是使用 Vue 來構建和分發自訂元素,你都會發現這是一個良好的選擇。

在 Vue 中使用自訂元素 - Using Custom Elements in Vue

Vue 在 Custom Elements Everywhere 測試中獲得了滿分 100%。在 Vue 應用中使用自訂元素基本上與使用原生 HTML 元素相同,但需要注意以下幾點:

跳過元件解析 - Skipping Component Resolution

預設情況下,Vue 會嘗試將非原生 HTML 標籤解析為已註冊的 Vue 元件,如果解析失敗,才會將其渲染為自訂元素。這會導致 Vue 在開發過程中發出「解析元件失敗」的警告。為了讓 Vue 知道某些元素應被視為自訂元素並跳過元件解析,我們可以指定 compilerOptions.isCustomElement 選項。

如果你使用 Vue 與建構設定,應通過建構配置來傳遞此選項,因為這是一個編譯時選項。

瀏覽器內部配置示例 - Example In-Browser Config

// 僅適用於在瀏覽器內部編譯。
// 如果使用建構工具,請參閱下面的配置示例。
app.config.compilerOptions.isCustomElement = (tag) => tag.includes('-')

Vite 配置示例 - Example Vite Config

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

export default {
plugins: [
vue({
template: {
compilerOptions: {
// 將所有帶有連字符的標籤視為自訂元素
isCustomElement: (tag) => tag.includes('-')
}
}
})
]
}

Vue CLI 配置示例 - Example Vue CLI Config

// 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 屬性 - Passing DOM Properties

由於 DOM 屬性只能是字串,我們需要將複雜數據作為 DOM 屬性傳遞給自訂元素。在設置自訂元素的 props 時,Vue 3 自動使用 in 運算符檢查 DOM 屬性的存在,如果鍵存在,將優先將其設置為 DOM 屬性。這意味著在大多數情況下,如果自訂元素遵循推薦的最佳實踐,你不需要考慮這一點。

然而,在某些罕見情況下,數據必須作為 DOM 屬性傳遞,但自訂元素未正確定義/反映該屬性(導致 in 檢查失敗)。在這種情況下,你可以使用 .prop 修飾符強制將 v-bind 綁定設置為 DOM 屬性:

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

<!-- 簡寫等價 -->
<my-element .user="{ name: 'jack' }"></my-element>
</template>

使用 Vue 建立自訂元素 - Building Custom Elements with Vue

自訂元素的主要優勢在於可以與任何框架(甚至不使用框架)搭配使用。這使得它們非常適合於分發給使用不同前端技術棧的終端使用者,或者在希望隔離終端應用程式與元件實現細節時使用。

defineCustomElement

Vue 支援通過 defineCustomElement 方法使用完全相同的 Vue 元件 API 來建立自訂元素。此方法接受與 defineComponent 相同的參數,但會返回一個擴展自 HTMLElement 的自訂元素構造函數:

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

const MyVueElement = defineCustomElement({
// 這裡是普通的 Vue 元件選項
props: {},
emits: {},
template: `...`,

// 只在 defineCustomElement 中使用: 將 CSS 注入 shadow root
styles: [`/* inlined css */`]
})

// 註冊自訂元素
// 註冊後,頁面上的所有 <my-vue-element> 標籤將被升級
customElements.define('my-vue-element', MyVueElement)

// 你也可以程序化地實例化元素:
// (只能在註冊後進行)
document.body.appendChild(
new MyVueElement({
// 初始化屬性(可選)
})
)

生命週期 - Lifecycle

  • 當元素的 connectedCallback 首次被調用時,Vue 自訂元素會在其 shadow root 內掛載一個內部的 Vue 元件實例。
  • 當元素的 disconnectedCallback 被調用時,Vue 將在一個微任務滴答後檢查該元素是否從文檔中分離。
  • 如果元素仍在文檔中,這是一次移動,元件實例將被保留;
  • 如果元素從文檔中分離,這是一次移除,元件實例將被卸載。

Props

所有通過 props 選項宣告的屬性都會作為屬性定義在自訂元素上。Vue 會自動處理屬性與屬性之間的反射。

  • 屬性總是反射到對應的屬性。
  • 帶有原始值(字串、布林值或數字)的屬性會被反射為屬性。

Vue 也會自動將用 Boolean 或 Number 類型宣告的屬性在設置為屬性(總是字串)時轉換為所需的類型。例如,給定以下屬性宣告:

props: {
selected: Boolean,
index: Number
}

以及自訂元素的使用:

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

在元件中,selected 將被轉換為 true(布林值),index 將被轉換為 1(數字)。

事件 - Events

通過 this.$emit 或 setup emit 發出的事件將作為原生的 CustomEvent 在自訂元素上分派。附加的事件參數(payload)將作為 CustomEvent 對象的 detail 屬性的一個數組暴露出來。

插槽 - Slots

在元件內部,可以像平常一樣使用 <slot/> 元素來渲染插槽。然而,當使用生成的元素時,它只接受原生的插槽語法

  • 不支援作用域插槽。

傳遞命名插槽時,請使用 slot 屬性而不是 v-slot 指令:

<my-element>
<div slot="named">hello</div>
</my-element>

提供 / 注入 - Provide / Inject

Provide / Inject API 及其組合式 API 等效方式也適用於 Vue 定義的自訂元素之間。但是,請注意,這僅適用於自訂元素之間,即 Vue 定義的自訂元素無法注入由非自訂元素 Vue 元件提供的屬性。

應用層級配置 - App Level Config 

你可以使用 configureApp 選項來配置 Vue 自訂元素的應用實例:

defineCustomElement(MyComponent, {
configureApp(app) {
app.config.errorHandler = (err) => {
/* ... */
}
}
})

作為自訂元素的 SFC - SFC as Custom Element

defineCustomElement 也適用於 Vue 單文件元件(SFC)。但是,使用預設的工具設定時,SFC 內的 <style> 會在生產環境構建時提取並合併到單個 CSS 文件中。使用 SFC 作為自訂元素時,通常希望將 <style> 標籤注入自訂元素的 shadow root 中。

官方的 SFC 工具支援以「自訂元素模式」導入 SFC(需要 @vitejs/plugin-vue@^1.4.0vue-loader@^16.5.0)。在自訂元素模式下加載的 SFC 會將其 <style> 標籤內聯為 CSS 字串,並在元件的 styles 選項下暴露。這將被 defineCustomElement 捕獲並在實例化時注入元素的 shadow root 中。

要選擇加入此模式,只需將元件文件名以 .ce.vue 結尾:

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

console.log(Example.styles) // ["/* inlined css */"]

// 轉換為自訂元素構造函數
const ExampleElement = defineCustomElement(Example)

// 註冊
customElements.define('my-example', ExampleElement)

如果希望自訂應以自訂元素模式導入的文件(例如,將所有 SFC 視為自訂元素),可以將 customElement 選項傳遞給相應的建構插件:

Vue 自訂元素庫的提示 - Tips for a Vue Custom Elements Library

使用 Vue 建立自訂元素時,這些元素將依賴於 Vue 的運行時。根據使用的功能數量,這大約會有 16kb 的基線大小成本。這意味著如果你只發布單個自訂元素,使用 Vue 並不理想——你可能希望使用原生 JavaScript、petite-vue 或專門針對小運行時大小的框架。然而,如果你發布的是具有複雜邏輯的自訂元素集合,這些基礎大小是完全可以接受的,因為 Vue 將使每個元件的編寫代碼大大減少。發布的元素越多,這種權衡越好。

如果自訂元素將在也使用 Vue 的應用中使用,你可以選擇從構建包中外部化 Vue,這樣元素將使用主機應用中的相同 Vue 副本。

建議導出個別的元素構造函數,以便用戶靈活地按需導入它們並使用所需的標籤名稱進行註冊。你也可以導出一個方便的函數來自動註冊所有元素。這是一個 Vue 自訂元素庫的示例入口點:

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)
}

如果你有很多元件,你還可以利用建構工具功能,例如 Vite 的全局導入或 webpack 的 require.context 來從目錄中加載所有元件。

Web Components 和 TypeScript

如果你正在開發應用或庫,可能希望對你的 Vue 元件進行類型檢查,包括那些定義為自訂元素的元件。

自定義元素是使用原生 API 全局註冊的,因此在 Vue 模板中使用時,默認情況下它們不會具有類型推斷。為了為註冊為自定義元素的 Vue 元件提供類型支持,我們可以在 Vue 模板和/或 JSX 中使用 GlobalComponents 介面註冊全局元件類型:

import { defineCustomElement } from 'vue'

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

// 將元件轉換為 Web 元素
export const Counter = defineCustomElement(CounterSFC)

// 註冊全局類型
declare module 'vue' {
export interface GlobalComponents {
Counter: typeof Counter
}
}

Web Components 與 Vue 元件​ - Web Components vs. Vue Components

有些開發者認為應該避免使用特定框架的元件模型,並認為只使用自定義元素(Custom Elements)可以使應用程式「未來安全」。在這裡,我們將嘗試解釋為什麼我們認為這是一個過於簡化的看法。

確實,自定義元素與 Vue 元件之間有一定程度的功能重疊:它們都允許我們定義可重用的元件,進行數據傳遞、事件發送和生命周期管理。然而,Web 元件的 API 相對較低級且基本。要構建實際的應用程式,我們需要許多額外的功能,而這些功能是平台本身不涵蓋的:

  • 一個宣告式且高效的模板系統;
  • 一個反應式狀態管理系統,以促進跨元件邏輯的提取和重用;
  • 一種高效的方式來在伺服器端渲染元件並在客戶端進行水合(SSR),這對於 SEO 和 Web Vitals 指標(如 LCP)非常重要。原生自定義元素的 SSR 通常涉及在 Node.js 中模擬 DOM,然後序列化變異的 DOM,而 Vue 的 SSR 盡可能編譯成字符串連接,這更加高效。

Vue 的元件模型是針對這些需求設計的,作為一個連貫的系統。

擁有一個能幹的工程團隊,你可能可以在原生自定義元素之上構建類似的系統——但這也意味著你將承擔一個內部框架的長期維護負擔,同時失去了成熟框架如 Vue 的生態系統和社群所帶來的好處。

也有一些框架是基於自定義元素作為其元件模型的基礎,但它們都不可避免地需要引入其專有的解決方案來處理上述問題。使用這些框架意味著接受它們在如何解決這些問題上的技術決策,這並不會如廣告所宣稱的那樣自動使你免受未來可能的變動影響。

此外,我們還發現自定義元素在某些方面具有局限性:

  • 積極的槽(slot)評估阻礙了元件的組合。Vue 的作用域插槽(scoped slots)是元件組合的一種強大機制,由於原生插槽的積極性,本質上不支持這一點。積極的插槽還意味著接收元件無法控制何時或是否渲染某個插槽內容。
  • 使用 shadow DOM 的自定義元素今天需要在 JavaScript 中嵌入 CSS,以便在運行時將其注入 shadow roots。它們在 SSR 場景中也會導致標記中的樣式重複。平台目前正在這方面開發一些功能,但目前這些功能還未得到普遍支持,並且還有一些生產性能 / SSR 的問題需要解決。與此同時,Vue 的 SFC 提供了支持將樣式提取為普通 CSS 文件的 CSS 範圍機制

Vue 將始終跟隨網頁平台的最新標準,我們也會樂於利用平台提供的任何功能來讓我們的工作變得更輕鬆。然而,我們的目標是提供工作良好且適用於今天的解決方案。這意味著我們必須以批判的態度來整合新平台功能,這涉及到在標準仍有不足時填補這些空缺。

這篇還是有點模糊~可能對於開發library蠻有幫助吧?ha~~








分享至
成為作者繼續創作的動力吧!
© 2024 vocus All rights reserved.