Vue3中如何使用defineCustomElement定义组件
Web Components 是一组 web 原生 API 的统称,允许开发者创建可复用的自定义元素 (custom elements)。
使用自定义元素的主要优势在于,它们可以在任何框架甚至非框架环境中使用。当你面向的最终用户可能使用了不同的前端技术栈,或是当你希望将最终的应用与它使用的组件实现细节解耦时,它们会是理想的选择。
Vue 和 Web Components 是互补的技术,Vue 为使用和创建自定义元素提供了出色的支持。在 Vue 应用中,你可以集成自定义元素,也可以使用 Vue 来构建和发布自定义元素。
Vue 在 Custom Elements Everywhere 测试中取得了 100% 的分数。在 Vue 应用中使用自定义元素基本上与使用原生 HTML 元素的效果相同,但需要进行一些额外的配置才能工作:
跳过组件解析默认情况下,Vue 会将任何非原生的 HTML 标签优先当作 Vue 组件处理,而将“渲染一个自定义元素”作为后备选项。这会在开发时导致 Vue 抛出一个“解析组件失败”的警告。
要让 Vue 知晓特定元素应该被视为自定义元素并跳过组件解析,我们可以指定 compilerOptions.isCustomElement 这个选项,设置在此选项对象上的值将会在浏览器内进行模板编译时使用,并会影响到所配置应用的所有组件。
另外也可以通过 compilerOptions 选项在每个组件的基础上覆盖这些选项(针对当前组件有更高的优先级)。
因为它是一个编译时选项,构建工具需要将配置传递给 @vue/compiler-dom:
vue-loader:通过 compilerOptions loader 的选项传递。
module.exports = {
chainWebpack: config =>
{
config.module
.rule('
vue'
)
.use('
vue-loader'
)
.tap(options =>
({
...options,
compilerOptions: {
// 将所有带 ion- 的标签名都视为自定义元素
isCustomElement: tag =>
tag.startsWith('
ion-'
)
}
}))
}
}
vite:通过 @vitejs/plugin-vue 的选项传递。
import vue from '
@vitejs/plugin-vue'
export default {
plugins: [
vue({
template: {
compilerOptions: {
// 将所有带短横线的标签名都视为自定义元素
isCustomElement: (tag) =>
tag.includes('
-'
)
}
}
})
]
}
浏览器内编译时的配置。
// 仅在浏览器内编译时才会工作
const app = createApp(App)
app.config.compilerOptions.isCustomElement = (tag) =>
tag.includes('
-'
) 传递 DOM 属性
由于 DOM attribute 只能为字符串值,因此我们只能使用 DOM 对象的属性来传递复杂数据。当为自定义元素设置 props 时,Vue 3 将通过 in 操作符自动检查该属性是否已经存在于 DOM 对象上,并且在这个 key 存在时,更倾向于将值设置为一个 DOM 对象的属性。这意味着,在大多数情况下,如果自定义元素遵循推荐的最佳实践,你就不需要考虑这个问题。
然而,也会有一些特别的情况:必须将数据以一个 DOM 对象属性的方式传递,但该自定义元素无法正确地定义/反射这个属性 (因为 in 检查失败)。在这种情况下,你可以强制使用一个 v-bind 绑定、通过 .prop 修饰符来设置该 DOM 对象的属性:
<my-element :user.prop="
{ name: '
jack'
}"
>
<
/my-element>
<
!-- 等价简写 -->
<
my-element .user="
{ name: '
jack'
}"
>
<
/my-element>
defineCustomElement()
Vue 提供了一个和定义一般 Vue 组件几乎完全一致的 defineCustomElement 方法来支持创建自定义元素。defineComponent 和这个方法所接收的参数完全一致。但它会返回一个继承自 HTMLElement 的原生自定义元素类的构造器(可以通过 customElements.define() 注册)。
function defineCustomElement(component:
| (ComponentOptions &
{ styles?: string[] })
| ComponentOptions['
setup'
]
): {
new (props?: object): HTMLElement
}
除了常规的组件选项,defineCustomElement() 还支持一个特别的选项 styles,它是一个内联 CSS 字符串的数组,所提供的 CSS 会被注入到该元素的 ShadowRoot 上。
<my-vue-element>
<
/my-vue-element>
import { defineCustomElement } from '
vue'
const MyVueElement = defineCustomElement({
// 这里是同平常一样的 Vue 组件选项
props: {},
emits: {},
template: `...`,
// defineCustomElement 特有的:注入进 ShadowRoot 的 CSS
styles: [`/* css */`]
})
// 注册自定义元素之后,所有此页面中的 `<
my-vue-element>
` 标签都会被升级
customElements.define('
my-vue-element'
, MyVueElement)
// 也可以在注册之后实例化元素:
document.body.appendChild(
new MyVueElement({
// 初始化 props(可选)
})
)
如果这时控制台报错:\color{red}{如果这时控制台报错:}如果这时控制台报错:Component provided template option but runtime compilation is not supported,在 vite.config.js 中添加以下配置:
resolve: { alias: { 'vue'
: '
vue/dist/vue.esm-bundler.js'
} }, 生命周期
当该元素的 connectedCallback 初次调用时,一个 Vue 自定义元素会在内部挂载一个 Vue 组件实例到它的 ShadowRoot 上。
当此元素的 disconnectedCallback 被调用时,Vue 会在一个微任务后检查元素是否还留在文档中。
如果元素仍然在文档中,那么说明它是一次移动操作,组件实例将被保留;
如果该元素不再存在于文档中,那么说明这是一次移除操作,组件实例将被销毁。
所有在 props 选项中声明的 props 都会被定义为自定义元素的属性。Vue 能够自动地适当处理它是否反射为属性或属性。
attribute 总是根据需要反射为相应的属性类型。原话已经非常清晰明了,可以直接重述。重新表述如下:
基本数据类型的属性值(例如字符串、布尔值或数字)可以通过反射反映为属性。当它们被设为 attribute 时 (永远是字符串),Vue 也会自动将以 Boolean 或 Number 类型声明的 prop 转换为所期望的类型。比如下面这样的 props 声明:
selected: Boolean,
index: Number
}
并以下面这样的方式使用自定义元素:
<my-element selected index="
1"
>
<
/my-element>
在组件中,selected 会被转换为 true (boolean 类型值) 而 index 会被转换为 1 (number 类型值)。
事件emit 触发的事件都会通过以 CustomEvents 的形式从自定义元素上派发。
额外的事件参数 (payload) 将会被暴露为 CustomEvent 对象上的一个 detail 数组。
在一个组件中,插槽将会照常使用 渲染。但是,使用最终元素时,只能使用原生插槽的语法,无法使用作用域插槽。
当传递具名插槽时,应使用 slot attribute 而不是 v-slot 指令:
my-element>
<
div slot="
named"
>
hello<
/div>
<
/my-element>
依赖注入
Provide / Inject API 和相应的组合式 API 在 Vue 定义的自定义元素中都可以正常工作。
但是,依赖关系只在自定义元素之间起作用。一个由常规 Vue 组件提供的属性无法被注入到由 Vue 定义的自定义元素中。
defineCustomElement 也可以搭配 Vue 单文件组件 (SFC) 使用。但是,根据默认的工具链配置,SFC 中的 <
style>
在生产环境构建时仍然会被抽取和合并到一个单独的 CSS 文件中。当正在使用 SFC 编写自定义元素时,通常需要改为注入 <
style>
标签到自定义元素的 ShadowRoot 上。
官方的 SFC 工具链支持以“自定义元素模式”导入 SFC (需要 @vitejs/plugin-vue@^1.4.0 或 vue-loader@^16.5.0)。一个以自定义元素模式加载的 SFC 将会内联其 <
style>
标签为 CSS 字符串,并将其暴露为组件的 styles 选项。这会被 defineCustomElement 提取使用,并在初始化时注入到元素的 ShadowRoot 上。
要开启这个模式,将组件文件以 .ce.vue 结尾即可:
// Example.ce.vue<
template>
<
h2>
Example.ce<
/h2>
<
/template>
<
script>
<
/script>
<
style>
h2 {
color: red;
}
<
/style>
import { defineCustomElement } from '
vue'
import Example from '
./Example.ce.vue'
console.log(Example.styles)
// 转换为自定义元素构造器
const ExampleElement = defineCustomElement(Example)
// 注册
customElements.define('
my-example'
, ExampleElement) 基于 Vue 构建自定义元素库
按元素分别导出构造函数,以便用户可以灵活地按需导入它们,还可以通过导出一个函数来方便用户自动注册所有元素。
// Vue 自定义元素库的入口文件import { defineCustomElement } from '
vue'
import Foo from '
./MyFoo.ce.vue'
import Bar from '
./MyBar.ce.vue'
const MyFoo = defineCustomElement(Foo)
const MyBar = defineCustomElement(Bar)
// 分别导出元素
export { MyFoo, MyBar }
export function register() {
customElements.define('
my-foo'
, MyFoo)
customElements.define('
my-bar'
, MyBar)
} defineComponent()
用来在定义 Vue 组件时为 TypeScript 提供类型推导的辅助函数。
对于一个 ts 文件,如果我们直接写 export default {},无法有针对性的提示 vue 组件里应该有哪些属性。
但是,增加一层 defineComponet 的话,export default defineComponent({}),就可以对参数进行一些类型推导和属性的提示等操作。
component: ComponentOptions | ComponentOptions['
setup'
]
): ComponentConstructor
参数是一个组件选项对象。返回值将是该选项对象本身,因为该函数实际上在运行时没有任何操作,仅用于提供类型推导,注意返回值的类型有一点特别:它是一个构造函数类型,它是根据选项推断出的组件实例类型。这样做可以在 TSX 中将返回值用作标签时提供类型推断支持。
你可以像这样从 defineComponent() 的返回类型中提取出一个组件的实例类型 (与其选项中的 this 的类型等价):
const Foo = defineComponent(/* ... */)type FooInstance = InstanceType<
typeof Foo>
defineAsyncComponent()
用来定义一个异步组件。在大型项目中,我们可能会将应用拆分成更小的模块,并在需要时从服务器加载相关组件。defineAsyncComponent 在运行时是懒加载的,参数可以是一个返回 Promise 的异步加载函数(resolve 回调方法应该在从服务器获得组件定义时调用),或是对加载行为进行更具体定制的一个选项对象。
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(() =>
{
return new Promise((resolve, reject) =>
{
// ...从服务器获取组件
resolve(/* 获取到的组件 */)
})
})
// ... 像使用其他一般组件一样使用 `AsyncComp`
// 也可以使用 ES 模块动态导入
const AsyncComp = defineAsyncComponent(() =>
import('
./components/MyComponent.vue'
)
)
得到的 AsyncComp 是一个外层包装过的组件,仅在页面需要它渲染时才会调用加载内部实际组件的函数。它会将接收到的 props 和插槽传给内部组件,所以你可以使用这个异步的包装组件无缝地替换原始组件,同时实现延迟加载。
与普通组件一样,异步组件可以使用 app.component() 全局注册:
app.component('MyComponent'
, defineAsyncComponent(() =>
import('
./components/MyComponent.vue'
)
))
也可以直接在父组件中直接定义它们:
<script setup>
import { defineAsyncComponent } from '
vue'
const AdminPage = defineAsyncComponent(() =>
import('
./components/AdminPageComponent.vue'
)
)
<
/script>
<
template>
<
AdminPage />
<
/template>
异步操作不可避免地会涉及到加载和错误状态,因此 defineAsyncComponent() 也支持在高级选项中处理这些状态:
const AsyncComp = defineAsyncComponent({// 加载函数
loader: () =>
import('
./Foo.vue'
),
// 加载异步组件时使用的组件
loadingComponent: LoadingComponent,
// 展示加载组件前的延迟时间,默认为 200ms
delay: 200,
// 加载失败后展示的组件
errorComponent: ErrorComponent,
// 如果提供了一个时间限制,并超时了,也会显示这里配置的报错组件,默认值是:Infinity
timeout: 3000
})
Vue3是一款非常受欢迎的JavaScript框架,而在Vue3中使用defineCustomElement定义组件也非常的简单。本文将为您详细介绍Vue3中如何使用defineCustomElement定义组件。
一、 什么是defineCustomElement?
defineCustomElement是Web Components的一部分,它是一个自定义元素创建器(custom element creator),可以用来在任何Web平台上创建自定义元素。首先,我们需要了解一下Web Components。
二、 什么是 Web Components?
Web Components是指W3C所规定的一种标准,它是一种基于浏览器功能(原生实现)的组件库,用于构建可组合的Web应用程序。Web Components由三个子规范组成:自定义元素、Shadow DOM和HTML模板。
三、 自定义元素
自定义元素可以理解为是一种拓展HTML元素的能力,使得我们可以自定义HTML标签,这样我们可以像使用div、p、span等HTML标签一样,使用我们自定义的标签。
四、Shadow DOM
Shadow DOM可以将HTML元素封装在一个DOM树中,从而保护元素和样式,避免全局样式影响。
五、 HTML模板
HTML模板也是Web Components的一部分,它是一种HTML片段,可以被复制,并用于渲染HTML。HTML模板通常与JavaScript一起使用,将模板渲染为可交互的Web应用程序。
六、Vue3中如何使用defineCustomElement 定义组件?
Vue3提供了defineCustomElement函数,我们可以使用它来定义我们自己的Web Components组件。
下面是一个使用defineCustomElement定义组件的例子:
```javascript
import { defineCustomElement } from 'vue'
import MyComponent from './MyComponent.vue'
const MyElement = defineCustomElement(MyComponent)
customElements.define('my-element', MyElement)
```
七、 总结
Vue3中使用defineCustomElement定义组件,可以让我们在任何Web平台上创建自定义元素,从而构建可组合的Web应用程序。Vue3提供了非常简单的API,可以让我们快速定义Web Components组件,是一款非常不错的JavaScript框架。