vue 如何使用 defineCustomElement

1,200 阅读2分钟

Web Components 是一组 web 原生 API 的统称,允许开发者创建可复用的自定义元素 (custom elements)。

创建自定义元素 defineCustomElement

Vue 提供了一个和定义 Vue 组件几乎完全一致的 defineCustomElement 方法来支持创建自定义元素。这个方法接收的参数和 defineComponent 完全相同。但它会返回一个继承自 HTMLElement 的自定义元素构造器:

<template>
    <test-element :info="info" :obj.prop="obj"><div>1</div></test-element>
</template>
<script lang="ts" setup>
  import { defineCustomElement, ref } from 'vue';

  const info = ref('ddd');
  const obj = ref({
    name: 'name',
  });
  
  const testElement = defineCustomElement({
    props: {
      info: String,
      obj: Object,
    },
    emits: {},
    template: `<div class="test">{{ info }} - {{ obj.name }}<slot/></div>`, 
    styles: [
      `.test { color: red; }`,
      '@import "http://localhost:6666/src/view/home/index.css";',
    ],
  });

  customElements.define('test-element', testElement);
</script>

默认情况下,Vue 会将任何非原生的 HTML 标签优先当作 Vue 组件处理,而将“渲染一个自定义元素”作为后备选项。要让 Vue 知晓特定元素应该被视为自定义元素并跳过组件解析

vite配置
// vite.config.js
import vue from '@vitejs/plugin-vue'

export default {
  plugins: [
    vue({
      template: {
        compilerOptions: {
          // 将所有带短横线的标签名都视为自定义元素
          isCustomElement: (tag) => tag.includes('test-')
        }
      }
    })
  ]
}

在vue这个构建中不支持运行时编译 需要配置你的 bundler 别名 vue: vue/dist/vue.esm-bundler.js

resolve: {
  alias: {
    'vue': 'vue/dist/vue.esm-bundler.js',
    '@': resolve(__dirname, 'src')
  }
}

props

所有使用props选项声明的props都会作为属性定义在改自定义元素上

事件

通过 this.$emit 或者 setup 中的 emit 触发的事件都会通过以 CustomEvents 的形式从自定义元素上派发。额外的事件参数 (payload) 将会被暴露为 CustomEvent 对象上的一个 detail 数组。

插槽

在一个组件中,插槽将会照常使用 <slot/> 渲染。然而,当使用最终的元素时,它只接受原生插槽的语法

  • 不支持作用域插槽

  • 当传递具名插槽时,应使用 slot attribute 而不是 v-slot 指令:

 <test-element>
    <div slot="named">hello</div>
 </test-element>

将 SFC 编译为自定义元素

defineCustomElement 也可以搭配 Vue 单文件组件 (SFC) 使用
要选用此模式,只需使用 .ce.vue 作为文件拓展名即可。

// item.ce.vue
<template>
  <h3> custom web components </h3>
  <ul>
    <li v-for="item in list" :key="item.name">
      姓名:{{ item.name }} - 年龄:{{ item.age }}
    </li>
  </ul>
</template>

<script lang="ts" setup>
  import { defineProps } from 'vue';

  defineProps({test
    list: {
      type: Array,
      default: () => [
        { name: 'y', age: 33 },
        { name: 'tom', age: 66 },
      ],
    },
  });
</script>

<style scoped>
  li {
    line-height: 36px;
  }
</style>
 import iconListCe from './item.ce.vue';

 const IconList = defineCustomElement(iconListCe);

 customElements.define('icon-list', IconList);