Vue3中使用defineCustomElement 定义组件详解

目录
  • 使用 Vue 构建自定义元素
    • 跳过组件解析
    • 传递 DOM 属性
  • defineCustomElement()
    • 生命周期
    • Props
    • 事件
    • 插槽
    • 依赖注入
    • 将 SFC 编译为自定义元素
    • 基于 Vue 构建自定义元素库
  • defineComponent()
  • defineAsyncComponent()

使用 Vue 构建自定义元素

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 的选项传递。
// vue.config.js
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 的选项传递。
// vite.config.js
import vue from '@vitejs/plugin-vue'
export default {
  plugins: [
    vue({
      template: {
        compilerOptions: {
          // 将所有带短横线的标签名都视为自定义元素
          isCustomElement: (tag) => tag.includes('-')
        }
      }
    })
  ]
}
  • 浏览器内编译时的配置。
// src/main.js
// 仅在浏览器内编译时才会工作
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 选项声明了的 props 都会作为属性定义在该自定义元素上。Vue 会自动地、恰当地处理其作为 attribute 还是属性的反射。
  • attribute 总是根据需要反射为相应的属性类型。基础类型的属性值 (string,boolean 或 number) 会被反射为 attribute。
  • 当它们被设为 attribute 时 (永远是字符串),Vue 也会自动将以 Boolean 或 Number 类型声明的 prop 转换为所期望的类型。比如下面这样的 props 声明:
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 组件所提供的属性。

将 SFC 编译为自定义元素

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>
  <h1>Example.ce</h1>
</template>
<script>
</script>
<style>
  h1 {
    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({}),就可以对参数进行一些类型推导和属性的提示等操作。
function 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中使用defineCustomElement 定义组件详解的详细内容,更多关于Vue3 defineCustomElement定义组件的资料请关注我们其它相关文章!

(0)

相关推荐

  • 详解vue3.2新增的defineCustomElement底层原理

    目录 Web Components customElements 概述 HTMLTemplateElement 内容模板元素 概述 常用属性 ShadowRoot 概述 Web Components Web Components 是一套不同的技术,允许您创建可重用的定制元素(它们的功能封装在您的代码之外)并且在您的web应用中使用它们. 相当于是浏览器原生的定义组件的方式,不用通过vue或者react这些框架实现组件的定义 customElements 概述 customElements 是Wi

  • Vue3使用setup如何定义组件的name属性详解

    目录 问题: 描述: 解决: 第一种: 第二种: 总结 问题: 解决Vue3中使用setup如何定义组件的name属性 描述: 这几天在尝试改写基于vue2的组件至vue3版本,使用了最新的setup也就是如下这种方式编写组件 <script lang="ts" setup> // ...代码块 </script> 由于个人还是比较喜欢Element那种组件注册方式,也就是如下 import TButton from './src/index.vue'; TBu

  • vue3 封装自定义组件v-model的示例

    首先要注意 vue3中 v-model 默认绑定的变量名变了,从原理的 value 改成了 modelValue,如果要改变变量的值,要执行一个事件 this.$emit("update:modelValue", value); <template> <div class="inline"> <input :type="password ? 'password' : 'text'" ref="input&q

  • vue实现自定义组件挂载原型上

    目录 自定义组件挂载原型上 以elementUI二次分装dialog举例 在原型上挂载方法和组件 挂在方法,在main.js中 挂载组件 自定义组件挂载原型上 以elementUI二次分装dialog举例 PageDialog.vue <!-- 页面提示弹框 --> <template>     <el-dialog       :visible.sync="show"       class="page-dialog-wrapper"

  • vue2中如何自定义组件的v-model

    目录 如何自定义组件的v-model 父组件 子组件 SearchTab 子组件示例 vue2与vue3在自定义组件v-model的区别 在vue2中,通常这样实现 在vue3中,通过这样实现 如何自定义组件的v-model 工作中经常会涉及到父子组件数据更新和页面相应数据样式改变的问题. 通俗点来说,就是涉及到公共组件的可以抽离出来作为子组件,如果父组件中与子组件相关的数据改变,子组件中的数据也会改变,如果子组件的数据改变,则需要做一定的处理才能改变父组件中的相关数据. 处理方式:考虑使用 自

  • vue3自定义组件之v-model实现父子组件双向绑定

    vue3 v-model父子组件双向绑定 vue3.x移除了vue2.x的model选项,自定义组件双向绑定不在使用以下方法: // vue2的v-model双向绑定方法 model: {      prop: 'value', //3.x默认值改为了modelValue      event: 'input' //3.x默认值改为了update:modelValue    }, //使用 this.$emit('input', index); vue3.x采用以下方式(v-model默认对应的

  • Vue3中使用defineCustomElement 定义组件详解

    目录 使用 Vue 构建自定义元素 跳过组件解析 传递 DOM 属性 defineCustomElement() 生命周期 Props 事件 插槽 依赖注入 将 SFC 编译为自定义元素 基于 Vue 构建自定义元素库 defineComponent() defineAsyncComponent() 使用 Vue 构建自定义元素 Web Components 是一组 web 原生 API 的统称,允许开发者创建可复用的自定义元素 (custom elements). 自定义元素的主要好处是,它们

  • Vue3中使用Supabase Auth方法详解

    目录 引言 安装Supabase 设置Supabase 创建一个AuthUser组合 创建页面 注册.vue EmailConfirmation.vue 登录.vu ForgotPassword.vue Me.vue login() loginWithSocialProvider() logout() isLoggedIn() register() update() sendPasswordResetEmail() 观察Auth状态的变化 测试东西 注销 家庭作业 总结 引言 Supabase是

  • Vue3中watch监听使用详解

    目录 Vue2使用watch Vue3使用watch 情况1 情况2 情况3 情况4 情况5 特殊情况 总结 Vue2使用watch <template> <div>总合:{{ sum }}<button @click="sum++">点击累加</button></div> </template> <script> import { ref } from "vue"; export

  • Vue3中setup方法的用法详解

    目录 1.参数props 2.参数context 3. 子组件向父组件派发事件 4.优化事件派发 5.获取父组件传递的值 1.参数props props是一个对象,包含父组件传递给子组件的所有数据.在子组件中使用props进行接收.包含配置声明并传入的所有的属性的对象. 也就是说,如果你想通过props的方式输出父组件传递给子组件的值.你需要使用props进行接收配置.即props:{......}.如果你未通过Props进行接受配置,则输出的值是undefined <template> &l

  • Vue3中的组合式 API示例详解

    目录 为什么要有组合式 API? 更好的逻辑复用 更灵活的代码组织 更好的类型推导 生产包体积更小 与选项式 API 的关系 组合式 API 是否覆盖了所有场景? 可以同时使用两种 API 吗? 选项式 API 会被废弃吗? 组合式 API 是一系列 API 的集合,使我们可以使用函数而不是声明选项的方式书写 Vue 组件.它是一个概括性的术语,涵盖了以下方面的 API: 响应性 API:例如ref()和reactive(),使我们可以直接创建响应式状态.计算属性和侦听器. 生命周期钩子:例如o

  • Vue3中ref与reactive的详解与扩展

    一.ref和reactive 死死记住:ref本质也是reactive,ref(obj)等价于reactive({value: obj}) vue3中实现响应式数据的方法是就是使用ref和reactive,所谓响应式就是界面和数据同步,能实现实时更新 vue2中响应式是通过defineProperty实现的,vue3中是通过ES6的Proxy来实现的 1.reactive reactive的参数必须是一个对象,包括json数据和数组都可以,否则不具有响应式 如果给reactive传递了其他对象(

  • 如何在Vue.js中实现标签页组件详解

    前言 标签页组件,即实现选项卡切换,常用于平级内容的收纳与展示. 因为每个标签页的内容是由使用组件的父级控制的,即这部分内容为一个 slot.所以一般的设计方案是,在 slot 中定义多个 div,然后在接到切换消息时,再显示或隐藏相关的 div.这里面就把相关的交互逻辑也编写进来了,我们希望在组件中处理这些交互逻辑,slot 只单纯处理业务逻辑.这可以通过再定义一个 pane 组件来实现,pane 组件嵌在 tabs 组件中. 1 基础版 因为 tabs 组件中的标题是在 pane 组件中定义

  • React中如何引入Angular组件详解

    前言 为了在我的编辑器中使用 Angular,我用 Angular 编写了一个重命名功能.而为了使用它,我得再次使用一次 customEvent ,而在这个微前端架构的系统中,其事件通讯机制已经相当的复杂.在这部分的代码进一步恶化之前,我得尝试有没有别的方式.于是,我想到了之前在其它组件中使用的 Web Components 技术,而 Angular 6 正好可以支持. 下面话不多说了,来一起看看详细的介绍吧 HTML 中引入 Web Components 我所需要做的事情也相当的简单,只需要将

  • Vue3中的极致防抖/节流详解(附常见方式防抖/节流)

    目录 前言 防抖或节流原理 防抖(debounce) 示例代码 使用 节流(throttle ) 示例代码 使用 环境说明 新封装 防抖(debounce) 常见封装-1 常见封装-2 新封装 使用 节流(throttle) 常见封装-1 常见封装-2 新封装 总结 前言 今天给大家带来的是Vue 3 中的极致防抖/节流(含常见方式防抖/节流)这篇文章,文章中不仅会讲述原来使用的防抖或节流方式,还会带来新的一种封装方式,使用起来更简单.更清晰. 在前端的开发过程中,在涉及到与用户交互的过程中是基

随机推荐