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

14,693 阅读1分钟

我们应该都写过类似这样的代码:

import { message } from 'antd'; message.success('成功')

那我们是否有考虑过自己也去封装一个这样的组件,在 js 中也能够导入使用?我把这个称为函数式组件,仔细看完我这篇文章,相信你也能够在 vue3 项目中封装自己的'函数式组件'。

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

  1. 最普通的导入式组件
  2. 全局组件(就类似于组件库 antd 的全局导入)
  3. 函数式组件(也就是能够在 js 代码中使用的组件,类似于 this.$message({})

组件目录结构

hAblcD.png

导入式组件

组件代码:/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.tsuse 这个 d.ts
import d from "@/config/d";
app.use(d)
  • 在页面中直接使用,不需要导入:/src/views/Home.vue
<d-back-button text="这是全局导入的自定义组件,不需要在页面中单独导入"></d-back-button>

函数式组件

函数式组件可以在 vue 文件中导入或者通过全局变量使用,也能在 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;
// 监听显示的消失,需要移除dom
watch(
  () => 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.tsuse 这个 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);
        }
    });
}

项目截图:

hAOnNn.png

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