Skip to content

Vue中的Web Components

Posted on:2022年6月30日 at 11:22

关于 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 componentsemits事件的接收

通过 this.$emit 或者 setup 中的 emit 触发的事件都会通过以 CustomEvents 的形式从自定义元素上派发。额外的事件参数 (payload) 将会被暴露为 CustomEvent 对象上的一个 detail 数组。 在父组件中我们可以通过ref获取到当前的web components,再通过侦听器addEventListener来捕获来自web components``Vue SFCemit事件,并可以接受其CustomEvent带来的emit payload

5. 效果

image

6. 吐槽

有 1 说 1,用起来不方便,尤其是子传父 🤢🤢🤢🤢🤢