前端(vue)入門到精通課程:進入學習
有時候想寫一個無關框架元件,又不想用原生或者 Jquery 那套去寫,而且還要避免樣式衝突,用 Web Components 去做剛覺就挺合適的。但是現在 Web Components 使用起來還是不夠靈活,很多地方還是不太方便的,如果能和 MVVM 搭配使用就好了。早在之前 Angular 就支援將元件構建成 Web Components,Vue3 3.2+ 開始終於支援將組建構建成 Web Components 了。正好最近想重構下評論外掛,於是上手試了試。
vue 提供了一個 defineCustomElement 方法,用來將 vue 元件轉換成一個擴充套件至HTMLElement的自定義函數建構函式,使用方式和 defineComponent 引數api基本保持一致。【相關推薦:】
import { defineCustomElement } from 'vue' const MyVueElement = defineCustomElement({ // 在此提供正常的 Vue 元件選項 props: {}, emits: {}, template: `...`, // defineCustomElement 獨有特性: CSS 會被注入到隱式根 (shadow root) 中 styles: [`/* inlined css */`] }) // 註冊 Web Components customElements.define('my-vue-element', MyVueElement)
如果需要使用單檔案,需要 @vitejs/plugin-vue@^1.4.0 或 vue-loader@^16.5.0 或更高版本工具。如果只是部分檔案需要使用,可以將字尾改為 .ce.vue 。若果需要將所有檔案都構建 Web Components 可以將 @vitejs/plugin-vue@^1.4.0 或 vue-loader@^16.5.0 的 customElement 設定項開啟。這樣不需要再使用 .ce.vue 字尾名了。
vue 會把所有的的 props 自定義元素的物件的 property 上,也會將自定義元素標籤上的 attribute 做一個對映。
<com-demo type="a"></com-demo> props:{ type:String }
因為 HTML 的 attribute 的只能是字串,除了基礎型別(Boolean、Number) Vue 在對映時會幫忙做型別轉換,其他複雜型別則需要設定到 DOM property 上。
在自定義元素中,通過 this.$emit 或在 setup 中的 emit 發出的事件會被排程為原生 CustomEvents。附加的事件引數 (payload) 會作為陣列暴露在 CustomEvent 物件的 details property 上。
編寫元件時,可以想 vue 一樣,但是使用時只能原生的插槽語法,所以也不在支援作用域插槽。
使用子元件巢狀的時,有個坑的地方就是預設不會將子元件裡的樣式抽離出來。
父元件
<template> <div>{{ title }}</div> <Childer /> </template> <script> import Childer from "./childer.vue" export default { components: { Childer }, data() { return { title: "父元件" } }, } </script> <style scoped> .title { padding: 10px; background-color: #eee; font-weight: bold; } </style>
子元件
<template> <div>{{ title }}</div> </template> <script> export default { data() { return { title: "子元件" } }, } </script> <style scoped> .childer { padding: 10px; background-color: #222; color: #fff; font-weight: bold; } </style>
可以看到子元件的樣式沒有插入進去,但是樣式隔離的標識是有生成的 data-v-5e87e937。不知道vue官方後續會不會修復這個bug
檢視元件是可以看到,子元件的樣式是有被抽離出來的,這樣就只需要自己注入進去了。
將子元件樣式抽離插入到父元件裡,參考這個的實現
import ComDemo from '~/demo/index.vue' const deepStylesOf = ({ styles = [], components = {} }) => { const unique = array => [...new Set(array)]; return unique([...styles, ...Object.values(components).flatMap(deepStylesOf)]); } // 將子元件樣式插入到父元件裡 ComDemo.styles = deepStylesOf(ComDemo) !customElements.get('com-demo') && customElements.define('com-demo', defineCustomElement(ComDemo))
完美解決子元件樣式問題
defineCustomElement 構建的元件預設是不會將方法掛到 customElement 上的,看 Vue 原始碼中,只有 _def(建構函式),_instance(元件範例))。如果想呼叫元件內的方法,dom._instance.proxy.fun(),感覺實在不太優雅。
我們當然希望我們元件暴露的方法能像普通dom那樣直接 dom.fun() 去掉用,我們對 defineCustomElement 稍作擴充套件。
import { VueElement, defineComponent } from 'vue' const defineCustomElement = (options, hydate) => { const Comp = defineComponent(options); class VueCustomElement extends VueElement { constructor(initialProps) { super(Comp, initialProps, hydate); if (Comp.methods) { Object.keys(Comp.methods).forEach(key => { // 將所有非下劃線開頭方法 繫結到 元素上 if(!/^_/.test(key)){ this[key] = function (...res) { if (this._instance) { // 將方法thi改為 元件範例的proxy return Comp.methods[key].call(this._instance.proxy, ...res) } else { throw new Error('未找到元件範例') } } } }) } } } VueCustomElement.def = Comp; return VueCustomElement; }
總體來說坑還是有不少的,如果僅僅需要構建一些比較簡單跨框架外掛,使用這種方式來構建 Web Components 也是一種不錯的方案。
更多程式設計相關知識,請存取:!!
以上就是聊聊怎麼用Vue3構建Web Components的詳細內容,更多請關注TW511.COM其它相關文章!