聊聊怎麼用Vue3構建Web Components

2022-09-08 22:00:32
如何使用 Vue3 構建 Web Components?下面本篇文章給大家介紹一下用 Vue3 構建 Web Components的方法,希望對大家有所幫助!

前端(vue)入門到精通課程:進入學習

有時候想寫一個無關框架元件,又不想用原生或者 Jquery 那套去寫,而且還要避免樣式衝突,用 Web Components 去做剛覺就挺合適的。但是現在 Web Components 使用起來還是不夠靈活,很多地方還是不太方便的,如果能和 MVVM 搭配使用就好了。早在之前 Angular 就支援將元件構建成 Web Components,Vue3 3.2+ 開始終於支援將組建構建成 Web Components 了。正好最近想重構下評論外掛,於是上手試了試。

構建 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.0vue-loader@^16.5.0 或更高版本工具。如果只是部分檔案需要使用,可以將字尾改為 .ce.vue 。若果需要將所有檔案都構建 Web Components 可以將 @vitejs/plugin-vue@^1.4.0vue-loader@^16.5.0customElement 設定項開啟。這樣不需要再使用 .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(),感覺實在不太優雅。
vuecode
我們當然希望我們元件暴露的方法能像普通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其它相關文章!