Vue3自定义插件:封装一个全局消息提示插件,所有组件都能直接用

📅 2026/6/16 1:39:54
Vue3自定义插件:封装一个全局消息提示插件,所有组件都能直接用
一、开头唠唠啥是插件跟我有啥关系你用 Vue 的时候肯定装过别人的插件。比如Vue Routerapp.use(router)之后所有组件里都能用this.$router跳转页面。Piniaapp.use(pinia)之后所有组件里都能用useXxxStore()管理状态。Element Plusapp.use(ElementPlus)之后所有组件里都能直接用el-button这些组件。这些插件都有一个共同特点只需要在 main.js 里写一行app.use(插件)整个项目都能用它的功能了。你有没有想过自己也能写一个这样的插件比如封装一个全局消息提示功能像 Element Plus 的ElMessage.success(操作成功)那样任何组件里都能调用弹出一个小提示几秒后自动消失。今天咱们就从零开始手把手把这个插件写出来。搞懂了这个你以后就能给自己的项目封装各种好用的工具了。二、插件到底是什么为什么长那样Vue 的插件本质上就是一个对象这个对象里必须有一个install方法。javascript// 一个最简单的插件长这样 const MyPlugin { install(app) { // app 是 Vue 的应用实例就是 createApp 返回的那个东西 // 你可以在这里做任何事注册全局组件、注入全局方法、添加全局指令等 } }当你写app.use(MyPlugin)时Vue 会自动调用MyPlugin.install(app)把应用实例传进去。然后你在install里做的一切操作都会应用到整个项目。打个比方app就像一个大房子install方法就是你在这个房子里装东西——装个门铃全局方法、装个书架全局组件、贴个标语全局指令。装完之后每个房间组件都能用。三、第一步先写一个最简单的插件雏形咱们先从最简单的开始写一个插件挂载一个全局方法$toast调用它就用alert弹窗。3.1 创建插件文件plugins/toast.jsjavascript// plugins/toast.js // 定义一个插件对象 const ToastPlugin { // install 是插件的入口Vue 在使用这个插件时会自动调用它 // app 参数是 createApp 返回的应用实例 install(app) { // app.config.globalProperties 是 Vue3 里专门用来挂载全局属性的地方 // 挂上去之后任何组件的 this 都能访问到 // 比如这里挂了一个 $toast 方法组件里就能用 this.$toast() 调用 app.config.globalProperties.$toast (message) { // 先简单点直接用浏览器自带的 alert 弹窗 alert(message) } } } // 导出插件让 main.js 能引入 export default ToastPlugin逐行解释const ToastPlugin { install(app) { ... } }定义一个对象里面有个install方法。这就是 Vue 插件的规定格式。app.config.globalProperties这是 Vue3 专门提供的“全局属性挂载点”。挂在上面的东西所有组件都能通过this访问到。$toast前面加个$是 Vue 的约定表示这是全局方法和组件自己的方法区分开。3.2 在main.js里注册插件javascript// main.js import { createApp } from vue import App from ./App.vue import ToastPlugin from ./plugins/toast.js // 引入刚才写的插件 const app createApp(App) // 使用插件一行搞定 app.use(ToastPlugin) app.mount(#app)3.3 在组件里调用vue!-- 任意组件 -- template div button clickshowMsg点我弹提示/button /div /template script setup // 在 script setup 里没有 this需要用 getCurrentInstance 获取组件实例 import { getCurrentInstance } from vue // getCurrentInstance 返回当前组件实例 // instance.proxy 就相当于选项式 API 里的 this const instance getCurrentInstance() function showMsg() { // 通过 proxy 调用全局方法 instance.proxy.$toast(你好这是插件弹出的消息) } /script注意在script setup里没有this所以要用getCurrentInstance().proxy来访问全局属性。稍后我们会封装一个更方便的调用方式。四、第二步让提示漂亮起来写一个消息组件用alert弹窗太丑了而且不能自定义样式。我们的目标是调用一个方法页面上出现一个漂亮的提示条带颜色、带图标几秒后自动消失。4.1 先写消息提示组件ToastMessage.vuevue!-- plugins/ToastMessage.vue -- template !-- Transition 是 Vue 内置的过渡组件包在它里面的元素出现和消失时会自动加动画 nametoast 表示动画类名以 toast 开头 -- Transition nametoast !-- visible 控制显示隐藏false 时元素会被移除 -- div v-ifvisible classtoast-message :classtype !-- 显示消息文字 -- {{ message }} /div /Transition /template script setup import { ref, onMounted } from vue // 接收父组件传来的参数 const props defineProps({ // 消息文本必传 message: { type: String, required: true }, // 消息类型不同类名对应不同背景色 type: { type: String, default: info // 默认是普通信息 }, // 显示时长单位毫秒默认 30003秒 duration: { type: Number, default: 3000 } }) // 控制组件显示/隐藏的开关 const visible ref(false) // 组件挂载后立即显示并在指定时间后隐藏 onMounted(() { // 先把开关打开触发进入动画 visible.value true // 到时间后关掉开关触发离开动画 setTimeout(() { visible.value false }, props.duration) }) /script style scoped .toast-message { /* 固定定位悬浮在页面顶部中央 */ position: fixed; top: 20px; left: 50%; /* translateX(-50%) 是水平居中的技巧 */ transform: translateX(-50%); padding: 10px 24px; border-radius: 4px; color: white; font-size: 14px; /* z-index 设大一点保证在最上层 */ z-index: 9999; /* 加个阴影让它看起来浮在页面上 */ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15); } /* 不同类型的背景色 */ .info { background-color: #909399; /* 灰色普通信息 */ } .success { background-color: #67c23a; /* 绿色成功 */ } .error { background-color: #f56c6c; /* 红色错误 */ } .warning { background-color: #e6a23c; /* 橙色警告 */ } /* -------- 进入和离开的过渡动画 -------- */ /* 进入的起始状态向上偏移 20px完全透明 */ .toast-enter-from { opacity: 0; transform: translate(-50%, -20px); } /* 离开的结束状态同样向上偏移并透明 */ .toast-leave-to { opacity: 0; transform: translate(-50%, -20px); } /* 进入和离开的过程0.3 秒平滑过渡 */ .toast-enter-active, .toast-leave-active { transition: all 0.3s ease; } /style逐行解释Transition nametoastVue 内置组件包裹需要动画的元素。nametoast意味着我们要用.toast-enter-from、.toast-leave-to这类类名写动画。v-ifvisible控制组件的显示和隐藏。visible从false变true时触发进入动画从true变false时触发离开动画。onMounted组件挂载后立即执行。先把visible设为true显示再设一个定时器到时间后设为false隐藏。CSS 里的position: fixed固定定位让提示条始终悬浮在页面上方不随滚动条移动。五、第三步升级插件让它动态创建组件现在有了ToastMessage组件但怎么在 JS 里动态创建它并挂到页面上呢思路调用$toast(消息)时用createApp创建一个新的 Vue 应用只包含ToastMessage组件。动态创建一个div作为挂载点插到body里。把组件挂上去页面就出现提示了。等动画播完卸载应用、移除 div清理干净。5.1 重写plugins/toast.jsjavascript// plugins/toast.js import { createApp } from vue import ToastMessage from ./ToastMessage.vue const ToastPlugin { install(app) { // 在全局挂载 $toast 方法 app.config.globalProperties.$toast (options) { // 如果传的是字符串转成对象格式 // 比如 this.$toast(操作成功) 转成 { message: 操作成功 } if (typeof options string) { options { message: options } } // 解构参数设置默认值 const { message, type info, duration 3000 } options // 1. 创建一个新的 Vue 应用只包含 ToastMessage 组件 // 第二个参数是传给组件的 props const toastApp createApp(ToastMessage, { message, // 消息文本 type, // 消息类型 duration // 显示时长 }) // 2. 动态创建一个 div作为组件的挂载点 const mountPoint document.createElement(div) // 把 div 加到 body 的最后面 document.body.appendChild(mountPoint) // 3. 把组件挂载到这个 div 上 // 此时页面上就会出现提示条了 toastApp.mount(mountPoint) // 4. 在动画结束后清理卸载应用 移除 div // 多等 500ms 是为了让离开动画播完 setTimeout(() { toastApp.unmount() // 卸载 Vue 应用 document.body.removeChild(mountPoint) // 从 body 中移除 div }, duration 500) } } } export default ToastPlugin逐行解释createApp(ToastMessage, { message, type, duration })用createApp创建一个新的 Vue 应用实例只渲染ToastMessage这一个组件。第二个参数是传给组件的props。document.createElement(div)纯原生 JS动态创建一个div元素。document.body.appendChild(mountPoint)把这个 div 加到body的最末尾。toastApp.mount(mountPoint)把 Vue 应用挂载到这个 div 上。挂载后ToastMessage组件就会被渲染到页面上。在duration 500毫秒后清理一切。多出的 500ms 是为了等 CSS 离开动画播完。六、第四步封装一个更方便的调用工具useToast每次都用instance.proxy.$toast()太啰嗦了。我们封装一个工具函数用起来更爽。6.1 创建utils/toast.jsjavascript// utils/toast.js import { getCurrentInstance } from vue // 导出一个函数组件里直接调用就能拿到各种提示方法 export function useToast() { // 获取当前组件实例 const instance getCurrentInstance() // proxy 就是组件实例的代理对象等同于选项式 API 里的 this const proxy instance.proxy // 返回一个对象包含各种快捷方法 return { // 通用方法可以传字符串或完整配置对象 toast: (options) proxy.$toast(options), // 快捷方法成功提示 success: (msg) proxy.$toast({ message: msg, type: success }), // 快捷方法错误提示 error: (msg) proxy.$toast({ message: msg, type: error }), // 快捷方法警告提示 warning: (msg) proxy.$toast({ message: msg, type: warning }), // 快捷方法普通信息提示 info: (msg) proxy.$toast({ message: msg, type: info }) } }6.2 在组件中使用vuetemplate div button clickshowSuccess成功提示/button button clickshowError失败提示/button button clickshowWarning警告提示/button button clickshowInfo普通提示/button /div /template script setup // 引入工具函数 import { useToast } from /utils/toast.js // 解构出需要的方法 const { success, error, warning, info } useToast() function showSuccess() { success(操作成功) } function showError() { error(操作失败请重试) } function showWarning() { warning(请注意这是警告信息) } function showInfo() { info(这是一条普通消息) } /script效果点不同按钮页面顶部会出现不同颜色的提示条3 秒后自动消失还带淡入淡出的动画。七、第五步防止重复提示叠加单例模式现在有个小问题如果快速点两个按钮页面上会同时出现两个提示条堆叠在一起。更好的体验是同一时间只显示一个提示新的会把旧的挤掉。7.1 升级plugins/toast.js加入单例逻辑javascript// plugins/toast.js完整版 import { createApp } from vue import ToastMessage from ./ToastMessage.vue // 用三个模块级变量存当前正在显示的 toast 信息 // 模块级变量在整个应用生命周期内只存在一份所有调用共享 let currentApp null // 当前正在运行的应用实例 let currentTimer null // 当前的销毁定时器 let currentMountPoint null // 当前的挂载点 DOM const ToastPlugin { install(app) { app.config.globalProperties.$toast (options) { if (typeof options string) { options { message: options } } const { message, type info, duration 3000 } options // -------- 关键步骤先销毁旧的再创建新的 -------- if (currentApp) { // 清除旧的定时器防止旧的把新的也干掉 clearTimeout(currentTimer) // 卸载旧的应用实例 currentApp.unmount() // 从 DOM 中移除旧的挂载点 document.body.removeChild(currentMountPoint) } // 创建新的应用实例 const toastApp createApp(ToastMessage, { message, type, duration }) const mountPoint document.createElement(div) document.body.appendChild(mountPoint) toastApp.mount(mountPoint) // 更新当前状态 currentApp toastApp currentMountPoint mountPoint currentTimer setTimeout(() { toastApp.unmount() document.body.removeChild(mountPoint) // 清理引用释放内存 currentApp null currentMountPoint null currentTimer null }, duration 500) } } } export default ToastPlugin解释currentApp、currentTimer、currentMountPoint三个变量定义在模块最外层不属于任何函数。这意味着它们在整个应用生命周期内只有一份所有$toast调用共享。每次调用$toast时先检查有没有旧的实例在运行。如果有清除定时器、卸载应用、移除 DOM确保旧的被彻底清理。然后再创建新的实例。这样就能保证同一时间只有一个提示条。八、完整项目结构textsrc/ ├── plugins/ │ ├── toast.js # 插件核心逻辑 │ └── ToastMessage.vue # 消息提示组件 ├── utils/ │ └── toast.js # useToast 工具函数 ├── main.js # 注册插件 └── App.vue # 使用示例main.js完整代码javascriptimport { createApp } from vue import App from ./App.vue import ToastPlugin from ./plugins/toast.js const app createApp(App) // 注册插件一行搞定 app.use(ToastPlugin) app.mount(#app)九、这个套路还能怎么用学会了动态创建组件的套路你可以封装更多有用的插件全局确认弹窗this.$confirm(确定删除吗).then(...)全局加载遮罩this.$loading.show()/this.$loading.hide()全局抽屉面板this.$drawer({ title: 设置, component: SettingsForm })核心思路都一样用createApp动态创建组件实例。动态创建挂载点插到 body 里。处理完自动清理。十、总结今天我们完整地封装了一个全局消息提示插件涉及的知识点步骤干什么关键代码定义插件写一个带install的对象const plugin { install(app) {...} }挂载全局方法让所有组件都能调用app.config.globalProperties.$toast ...动态创建组件在 JS 里渲染 Vue 组件createApp(组件, props).mount(div)单例模式同一时间只显示一个模块级变量存当前实例新的销毁旧的过渡动画提示条淡入淡出Transition CSS 类名学会了自定义插件你就从“用工具的人”变成了“造工具的人”。以后团队里谁需要什么通用功能你甩一个插件过去大家都方便。有问题评论区说我挨个回。下篇咱们聊权限控制动态路由、按钮权限一次性搞定