vue3如何封装一个在js中也能使用的全局组件?vue3的三种组件封装形式(导入式组件、全局组件、函数式组件),建议收藏
在我看来,vue3的开发中应该有三种组件封装形式,分别是
最普通的导入式组件
全局组件(就类似于组件库antd的全局导入)
函数式组件(也就是能够在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><Modalv-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~
