vlambda博客
学习文章列表

vue3如何封装一个在js中也能使用的全局组件?vue3的三种组件封装形式(导入式组件、全局组件、函数式组件),建议收藏

在我看来,vue3的开发中应该有三种组件封装形式,分别是

  1. 最普通的导入式组件

  2. 全局组件(就类似于组件库antd的全局导入)

  3. 函数式组件(也就是能够在js代码中使用的组件,类似于this.$message({}))

组件目录结构

导入式组件

组件代码:/src/components/import/BButton.vue

<template> <a-button @click="clickBack" type="primary" plain> {{ text }} </a-button></template>
<script setup>import { useRouter } from "vue-router";const props = defineProps({ text: { type: String, default: "返回" }, backLevel: { type: Number, default: 1, },});
const router = useRouter();const clickBack = () => { router.go(-props.backLevel);};</script>

在页面导入使用:/src/views/Home.vue

import BButton from '@/components/import/BButton.vue'<b-button text="最简单的导入组件"></b-button>

全局组件

组件代码:/src/components/common/BackButton.vue

<template> <a-button @click="clickBack" type="primary" plain> {{text}} </a-button></template>
<script setup>import { useRouter } from "vue-router";const props = defineProps({ text: { type: String, default: "返回" }, backLevel: { type: Number, default: 1, },});
const router = useRouter();const clickBack = () => { router.go(-props.backLevel);};</script>

全局导入:/src/config/d.ts

//通过vite提供的import.meta.globEager读取common目录下的所有vue组件,并且以 D+文件名 作为组件名。//在使用时使用 d-组件名的形式const componentList = import.meta.globEager('../components/common/**');let componentArray = new Object()Object.keys(componentList).forEach((key) => { let keyArray = key.split('/') let name = 'D' + keyArray[keyArray.length - 1].split('.')[0] componentArray[name] = componentList[key].default})
export default function (app) { Object.keys(componentArray).forEach((key) => { app.component(key, componentArray[key]) })}

在main.ts中use这个d.ts

import d from "@/config/d";app.use(d)

在页面中直接使用,不需要导入:/src/views/Home.vue

<d-back-button text="这是全局导入的自定义组件,不需要在页面中单独导入"></d-back-button>

函数式组件

这里我将会介绍如何在vue文件中使用,以及在ts/js文件中使用。

我们可能会遇到一个场景,比如说需要在接口报错或者成功时弹出一个全局自定义的组件,要求不能在vue文件中去写,也不能使用组件库的组件,而是需要在axios这种js/ts文件中去写,这样才能做到通用。那么我们该如何在js中使用并封装一个函数式组件?


组件代码:/src/components/function/components/tipsDialog.vue

<template> <Modal v-model:visible="pageVisible" title="自定义全局函数组件" @ok="_sure" @cancel="pageVisible = false" :okText="okText" > {{content}} </Modal></template>
<script setup>import { ref, watch } from "vue";//自定义函数组件无法使用全局组件,需要单独引入import { Modal } from "ant-design-vue";const props = defineProps({ visible: { type: Boolean, default: false, }, okText: { type: String, default: "确定", }, handleOk: { type: Function, //成功回调 default: null, }, remove: { type: Function, //传入移除节点方法,这里是createApp中的方法 default: null, }, content:{ type: String, default: "自定义全局函数组件......", }});
const pageVisible = ref(false);pageVisible.value = props.visible;// 监听显示的消失,需要移除domwatch( () => pageVisible.value, (val) => { !val && props.remove(); });
// 确认const _sure = () => { typeof props.handleOk === "function" && props.handleOk("组件参数"); pageVisible.value = false;};</script>

组件代码:/src/components/function/components/tipsDialog.ts

import { createApp } from 'vue';import FunTipsDialog from './tipsDialog.vue'// 使用vue3的createApp,以及mount,unmount方法创建挂载实例
export default function TipsDialog(options) { // 创建一个节点,并将组件挂载上去 const mountNode = document.createElement('div') document.body.appendChild(mountNode) const app = createApp(FunTipsDialog, { ...options, visible: true, remove() { app.unmount(mountNode) //创建完后要进行销毁 document.body.removeChild(mountNode) } }) return app.mount(mountNode)}

组件代码:/src/components/function/index.ts

//使用import.meta.globEager读取components文件夹的文件,以后缀名ts区分const componentsList = import.meta.globEager("./components/**");
let List = {}; export default function (app) { Object.keys(componentsList).forEach((key) => { // 筛选出ts后缀 if (key.split(".")[2] === "ts") { //赋值函数组件,后面抛出,导入使用 List[`$${componentsList[key].default.name}`] = componentsList[key].default;
//将函数组件定义到全局变量中,在vue中的script中通过proxy使用 app.config.globalProperties[`$${componentsList[key].default.name}`] = componentsList[key].default; } });}
//抛出函数组件,用于导入使用export const funComponentList = List;

在main.ts中use这个index.ts

import fc from "@/components/function/index"app.use(fc)

在vue中使用 /src/views/Home.vue

//通过proxy获取全局对象使用<a-button @click="clickOpenFunComponent">这是自定义全局函数组件,点击打开</a-button>
import { getCurrentInstance } from "vue";const { proxy } = getCurrentInstance();const clickOpenFunComponent = () => { proxy.$TipsDialog({ handleOk: (str) => { console.log("点击成功,可以在此处做回调操作。"+str); }, });};
//也可以导入使用import { funComponentList } from "@/components/function/index";funComponentList.$TipsDialog({ content:"在request.ts触发的函数式组件", handleOk: (str) => { console.log("点击成功,可以在此处做回调操作。"+str); }    });

在request.ts中使用,当调用接口成功或报错时弹出 /src/config/request.ts

import { funComponentList } from "@/components/function/index";
if (response?.status === 200) { funComponentList.$TipsDialog({ content:"在request.ts触发的函数式组件", handleOk: (str) => { console.log("点击成功,可以在此处做回调操作。"+str); } });}

项目截图:

项目目前引入了: i18n vuex v-router less mock axios封装 ant-design(按需加载) sentry 构建分包 env ts的支持 三种封装组件的形式 Jsx的支持。赏个star~