EP63 - Vue與Web組件

閱讀時間約 18 分鐘
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~~








2會員
70內容數
分享生活趣事~
留言0
查看全部
發表第一個留言支持創作者!
卡關的人生 的其他內容
Vue 建議使用模板構建應用程式,但在需要 JavaScript 的全程式化功能時,渲染函數可派上用場。渲染函數通過 h() 函數創建 vnode,h 是 hyperscript 的簡寫,能生成 HTML 的 JavaScript。
Vue 渲染機制使用虛擬 DOM 將模板轉換為實際的 DOM 節點,並高效地更新這些節點。虛擬 DOM 是 UI 的內存表示,與真實 DOM 同步。渲染過程分為編譯、掛載和修補三個步驟。編譯將模板轉為渲染函數,掛載遍歷虛擬 DOM 樹並構建真實 DOM 樹,修補則比較新舊虛擬 DOM 樹並應用變更。
Vue 的反應性系統利用了 JavaScript 的 Proxy 和 getter/setter 機制,使得對物件屬性的變更能夠自動觸發視圖更新。這種無侵入性的設計使得狀態管理變得簡單且直觀,無需手動同步視圖。反應性的核心在於追蹤依賴項和觸發副作用。
Composition API 是 Vue 3 的一組 API,允許使用導入的函數編寫 Vue 組件,涵蓋反應性 API、生命週期鉤子和依賴注入。它主要在單文件組件的 <script setup> 語法中使用。
Vue 是一個靈活且可逐步採用的框架,適用於不同使用情境以平衡技術棧複雜度、開發者體驗和最終性能。Vue 可以作為獨立腳本文件使用,不需構建步驟,適合簡單前端邏輯。也可用來構建標準 Web 組件,嵌入到任何 HTML 頁面中。對於需要豐富互動性的應用,可構建單頁應用程式 (SPA)。
項目 API 涉及整體應用的全局屬性及配置,通常透過 TypeScript 的模組擴充來增強全局屬性,如 app.config.globalProperties。
Vue 建議使用模板構建應用程式,但在需要 JavaScript 的全程式化功能時,渲染函數可派上用場。渲染函數通過 h() 函數創建 vnode,h 是 hyperscript 的簡寫,能生成 HTML 的 JavaScript。
Vue 渲染機制使用虛擬 DOM 將模板轉換為實際的 DOM 節點,並高效地更新這些節點。虛擬 DOM 是 UI 的內存表示,與真實 DOM 同步。渲染過程分為編譯、掛載和修補三個步驟。編譯將模板轉為渲染函數,掛載遍歷虛擬 DOM 樹並構建真實 DOM 樹,修補則比較新舊虛擬 DOM 樹並應用變更。
Vue 的反應性系統利用了 JavaScript 的 Proxy 和 getter/setter 機制,使得對物件屬性的變更能夠自動觸發視圖更新。這種無侵入性的設計使得狀態管理變得簡單且直觀,無需手動同步視圖。反應性的核心在於追蹤依賴項和觸發副作用。
Composition API 是 Vue 3 的一組 API,允許使用導入的函數編寫 Vue 組件,涵蓋反應性 API、生命週期鉤子和依賴注入。它主要在單文件組件的 <script setup> 語法中使用。
Vue 是一個靈活且可逐步採用的框架,適用於不同使用情境以平衡技術棧複雜度、開發者體驗和最終性能。Vue 可以作為獨立腳本文件使用,不需構建步驟,適合簡單前端邏輯。也可用來構建標準 Web 組件,嵌入到任何 HTML 頁面中。對於需要豐富互動性的應用,可構建單頁應用程式 (SPA)。
項目 API 涉及整體應用的全局屬性及配置,通常透過 TypeScript 的模組擴充來增強全局屬性,如 app.config.globalProperties。
你可能也想看
Google News 追蹤
Thumbnail
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
Faker昨天真的太扯了,中國主播王多多點評的話更是精妙,分享給各位 王多多的點評 「Faker是我們的處境,他是LPL永遠繞不開的一個人和話題,所以我們特別渴望在決賽跟他相遇,去直面我們的處境。 我們曾經稱他為最高的山,最長的河,以為山海就是盡頭,可是Faker用他28歲的年齡...
Thumbnail
各位使用 Vue.js 開發的小夥伴們,你們都怎麼實作父子層組件資料的雙向綁定呢?如果你還在寫 prop + emit 的話,不妨進來看看吧。
Thumbnail
※ 什麼是Web API API 就是後端開出來讓前端來用的介面,讓前端與後端可以溝通。 API流程: 終端使用者用任何一種裝置進入瀏覽器。 瀏覽器透過 API 向後端發出請求,請求查詢或修改資料。 後端透過 API 收到前端的請求後,取得資料並回應給前端。 前端渲染畫面,終端使用者
Thumbnail
切換頁面卡卡有很多種原因,這裡舉的例子只針對元件太大的情境。 除了想辦法拆分外,還有一個方法就是利用 Vue 的 Async Component。
Thumbnail
一開始你先把你的專案push上去後,修改vite.config.ts ,要在裡面新增  base: "/Cart/" (/放自己的專案名稱/) build: {outDir: "docs"}, 接下來你要去你的github setting 裡面 -> Page ->選Deploy fro
Thumbnail
你好,在下最近在學習開發web,學了html css js,也得出一些心得,由於網路上已有許多教學,所以我會著重在如何開發出to do List,以及解釋我寫的程式碼。相關的教學我會直接貼網址。如果我有什麼地方出錯,或者是可以寫得更好,歡迎在下方留言,討論。 首先先介紹我的開發環境: 我用了vs
Thumbnail
在 TypeScript 中,套件是模組化代碼的集合,可以提高代碼的可重用性和可維護性。常見的套件包括各種庫和框架,如 lodash、express 等。以下是有關引用套件、自定義套件和常見套件的詳細介紹。
Thumbnail
因為最近想嘗試編碼風格,於是就選了一套比較"不嚴格"的輔助工具來摸索。 編輯器 VS CODE 框架 VUE3 打包工具 VITE 編碼風格 Standard 環境 version { "nodejs":"v18.18.0", "npm":"9.8.1" }
Thumbnail
平常我們在 html 上常看到的例如 v-for、v-model 等等... 也是VUE已經幫我們定義好的指令,而這次我們可以依這自己的需求來建立。 此功能屬於較進階的功能,因此實戰中會比較少見,市面上還是有不少完善的套件能達到同樣效果,建議可以先往這方面察找
Thumbnail
我們在實作中,難免會遇到在不同組件中,卻有需求相同的資料格式,因此 mixins 可以達到我們的需求,除了 data 以外也包含了 methods 可以共用,舉例來說,學生資料可能會在,班級跟社團內被使用,當我們要撰寫元件時,就可以省略多餘的 data 定義。
Thumbnail
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
Faker昨天真的太扯了,中國主播王多多點評的話更是精妙,分享給各位 王多多的點評 「Faker是我們的處境,他是LPL永遠繞不開的一個人和話題,所以我們特別渴望在決賽跟他相遇,去直面我們的處境。 我們曾經稱他為最高的山,最長的河,以為山海就是盡頭,可是Faker用他28歲的年齡...
Thumbnail
各位使用 Vue.js 開發的小夥伴們,你們都怎麼實作父子層組件資料的雙向綁定呢?如果你還在寫 prop + emit 的話,不妨進來看看吧。
Thumbnail
※ 什麼是Web API API 就是後端開出來讓前端來用的介面,讓前端與後端可以溝通。 API流程: 終端使用者用任何一種裝置進入瀏覽器。 瀏覽器透過 API 向後端發出請求,請求查詢或修改資料。 後端透過 API 收到前端的請求後,取得資料並回應給前端。 前端渲染畫面,終端使用者
Thumbnail
切換頁面卡卡有很多種原因,這裡舉的例子只針對元件太大的情境。 除了想辦法拆分外,還有一個方法就是利用 Vue 的 Async Component。
Thumbnail
一開始你先把你的專案push上去後,修改vite.config.ts ,要在裡面新增  base: "/Cart/" (/放自己的專案名稱/) build: {outDir: "docs"}, 接下來你要去你的github setting 裡面 -> Page ->選Deploy fro
Thumbnail
你好,在下最近在學習開發web,學了html css js,也得出一些心得,由於網路上已有許多教學,所以我會著重在如何開發出to do List,以及解釋我寫的程式碼。相關的教學我會直接貼網址。如果我有什麼地方出錯,或者是可以寫得更好,歡迎在下方留言,討論。 首先先介紹我的開發環境: 我用了vs
Thumbnail
在 TypeScript 中,套件是模組化代碼的集合,可以提高代碼的可重用性和可維護性。常見的套件包括各種庫和框架,如 lodash、express 等。以下是有關引用套件、自定義套件和常見套件的詳細介紹。
Thumbnail
因為最近想嘗試編碼風格,於是就選了一套比較"不嚴格"的輔助工具來摸索。 編輯器 VS CODE 框架 VUE3 打包工具 VITE 編碼風格 Standard 環境 version { "nodejs":"v18.18.0", "npm":"9.8.1" }
Thumbnail
平常我們在 html 上常看到的例如 v-for、v-model 等等... 也是VUE已經幫我們定義好的指令,而這次我們可以依這自己的需求來建立。 此功能屬於較進階的功能,因此實戰中會比較少見,市面上還是有不少完善的套件能達到同樣效果,建議可以先往這方面察找
Thumbnail
我們在實作中,難免會遇到在不同組件中,卻有需求相同的資料格式,因此 mixins 可以達到我們的需求,除了 data 以外也包含了 methods 可以共用,舉例來說,學生資料可能會在,班級跟社團內被使用,當我們要撰寫元件時,就可以省略多餘的 data 定義。