Vue3.2 Vue与Web Components的爱恨情仇
关于
Web Components
关于Vue3.2
definecustomelement
Api
关于Vue & Web Component
在Vue3.2
中实现一个dialog
1. 修改vite.config.ts
import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'
// web components 的 tag name
const webComponents = ['my-dialog']
export default defineConfig({
plugins: [vue({
template: {
compilerOptions: {
// 告知 vite 这些 tag 是 自定义元素,不以 vue components 来解析
isCustomElement: (tag) => webComponents.includes(tag)
}
}
})]
})
2. 新建src/web-components
文件夹
_1. 新建 index.ce.vue
关于
.ce.vue
:
defineCustomElement
搭配Vue
单文件组件 (SFC
) 使用时,SFC
中的<style>
在生产环境构建时仍然会被抽取和合并到一个单独的CSS
文件中。当正在使用SFC
编写自定义元素时,通常需要改为注入<style>
标签到自定义元素的shadow root
上。
官方的SFC
工具链支持以“自定义元素模式”导入SFC
(需要@vitejs/plugin-vue@^1.4.0
(vite
中) 或vue-loader@^16.5.0
(webpack(vue-cli)
中) )。一个以自定义元素模式加载的SFC
将会内联其<style>
标签为CSS
字符串,并将其暴露为组件的styles
选项。这会被defineCustomElement
提取使用,并在初始化时注入到元素的shadow root
上。
要开启这个模式,只需要将你的组件文件以.ce.vue
结尾即可!
<script setup lang="ts">
defineProps<{
open: boolean | null
}>()
const emits = defineEmits<{
(e: 'update:open', show: boolean): void
}>()
const hide = () => {
emits('update:open', false)
}
</script>
<template>
<dialog :open="open">
<slot>hello</slot>
<button @click="hide">confirm</button>
</dialog>
</template>
<style>
:host(:not([open])) {
display: none;
}
:host {
position: fixed;
left: 0; top: 0;
height: 100%; width: 100%;
background-color: rgba(25, 28, 34, 0.88);
z-index: 19;
display: grid;
place-items: center;
}
:host dialog {
position: static;
display: inherit;
}
</style>
_2. 新建 index.ts
import {defineCustomElement} from 'vue'
import MyDialog from './index.ce.vue'
const registerMyDialog = () => {
customElements.define('my-dialog', defineCustomElement(MyDialog))
}
export default registerMyDialog
3. 在 main.ts
中注册
import {createApp} from 'vue'
import App from './App.vue'
import registerMyDialog from './web-components/MyDialog'
registerMyDialog()
const app = createApp(App)
app.mount('#app')
4. 使用 App.vue
为例
<script setup lang="ts">
import {onMounted, onUnmounted, ref} from "vue";
const show = ref(false)
const dialogRef = ref<HTMLElement | null>(null)
const handleUpdateShow = (e: Event) => {
const [_show] = <[boolean]>((<CustomEvent>e).detail)
show.value = _show
}
onMounted(() => {
dialogRef.value?.addEventListener('update:open', handleUpdateShow)
})
onUnmounted(() => {
dialogRef.value?.removeEventListener('update:open', handleUpdateShow)
})
</script>
<template>
<div id="app">
<button @click="show = true">show dialog</button>
<my-dialog ref="dialogRef" .open="show">
<h1>wo cao nb</h1>
</my-dialog>
</div>
</template>
关于
props
当我们为
web components
传递参数时,Vue 3
将通过in
操作符自动检查该属性是否已经存在于DOM
对象上,并且在这个key
存在时,更倾向于将值设置为一个DOM
对象的属性。
因为由于DOM attribute
只能为字符串值,所以当我们需要为web components
传递复杂数据时候,可以使用DOM
对象的属性来传递数据:
即:通过.prop
修饰符来设置该DOM
(web cpmponents
) 对象的属性
<my-element :user.prop="{ name: 'jack' }"></my-element>
<!-- 等价简写 -->
<my-element .user="{ name: 'jack' }"></my-element>
关于
web components
的emits
事件的接收通过
this.$emit
或者setup
中的emit
触发的事件都会通过以CustomEvents
的形式从自定义元素上派发。额外的事件参数 (payload
) 将会被暴露为CustomEvent
对象上的一个detail
数组。
在父组件中我们可以通过ref
获取到当前的web components
,再通过侦听器addEventListener
来捕获来自web components``Vue SFC
的emit
事件,并可以接受其CustomEvent
带来的emit payload
5. 效果
6. 吐槽
有1说1,用起来不方便,尤其是子传父 🤢🤢🤢🤢🤢