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