关于
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,用起来不方便,尤其是子传父 🤢🤢🤢🤢🤢