Vue3.2 Vue与Web Components的爱恨情仇

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

posted @ 2022-06-30 11:22  demo_you  阅读(1131)  评论(1编辑  收藏  举报