Vue3 里的全局函数是啥?和 Vue2 有啥不一样?
不少刚接触 Vue3 的开发者,总会疑惑全局函数该咋用,和 Vue2 时期的写法差别在哪?其实全局函数在项目里能帮我们复用工具逻辑、统一处理通用功能,理解透它的用法和变化,能让代码更简洁高效,接下来就从基础到进阶,把 Vue3 全局函数的门道聊明白。
简单说,全局函数就是整个 Vue 项目里,任何组件都能直接调用的函数,比如处理时间格式化、判断用户权限这类通用逻辑,不用在每个组件里重复写代码或者 import,省事儿又好维护。
但 Vue3 与 Vue2 注册全局函数的方式完全不同——
Vue2 是直接往 Vue.prototype
上“挂”方法,举个例子,若想全局注册一个打招呼函数:
import Vue from 'vue' Vue.prototype.$sayHi = () => alert('Hi~')
组件里用 this.$sayHi()
就能调用,因为所有组件实例的 this
会“继承” Vue.prototype
上的属性。
Vue3 换了思路:需用 createApp
创建应用实例(app),再通过 app.config.globalProperties
注册全局函数,还是上面的例子,Vue3 得这么写:
import { createApp } from 'vue' import App from './App.vue' <p>const app = createApp(App) app.config.globalProperties.$sayHi = () => alert('Hi~') app.mount('#app')
改动的原因是 Vue3 更支持“多应用共存”(比如一个页面里同时运行多个 Vue 应用),用应用实例(app)管理全局配置,每个 app 可拥有自己的全局函数,互不干扰,像做微前端项目时,不同子应用的全局函数不会冲突,灵活性更高。
Vue3 注册全局函数分几步?组件里咋调用?
注册与调用可拆成“三步走”,看例子便知。
第一步:创建应用实例,注册全局函数
先在入口文件(如 main.js
)里,用 createApp
包裹根组件,得到 app 实例,再通过 app.config.globalProperties
挂函数。
举个实际场景:注册一个时间格式化函数,将时间字符串转成“年/月/日”格式,代码如下:
// main.js import { createApp } from 'vue' import App from './App.vue' <p>const app = createApp(App)</p> <p>// 注册全局时间格式化函数 app.config.globalProperties.$formatDate = (dateStr) => { const date = new Date(dateStr) return <code>${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}</code> }</p> <p>app.mount('#app')
第二步:选项式组件里调用(用 this)
要是选项式组件(比如用 export default { ... }
写的组件),直接通过 this.$xxx
调用全局函数。
比如一个展示格式化时间的组件:
<template> <div>原始时间:{{ rawDate }} → 格式化后:{{ formattedDate }}</div> </template> <p><script> export default { data() { return { rawDate: '2024-09-15' // 假设后端返回的时间字符串 } }, computed: { formattedDate() { // 调用全局函数$formatDate return this.$formatDate(this.rawDate) } } } </script>
第三步:组合式组件里调用(用 getCurrentInstance)
要是组合式组件(用 <script setup>
语法糖),因 setup
里没有 this
,得用 getCurrentInstance
获取组件实例的上下文,再调用全局函数。
还是上面的例子,改成组合式写法:
<template> <div>原始时间:{{ rawDate }} → 格式化后:{{ formattedDate }}</div> </template> <p><script setup> import { computed, getCurrentInstance } from 'vue'</p> <p>const rawDate = '2024-09-15'</p> <p>// 获取组件实例的proxy(代理对象) const { proxy } = getCurrentInstance()</p> <p>// 计算属性里调用全局函数 const formattedDate = computed(() => { return proxy.$formatDate(rawDate) }) </script>
这里要注意:getCurrentInstance
在生产环境(打包后)可能因代码优化(如 tree - shaking)出问题,后面会讲更稳妥的封装方式,规避这个坑。
哪些场景适合用全局函数?别瞎用!
全局函数并非万能,用错会让代码愈发混乱,需满足“通用、复用、无状态/轻状态”这些条件,才适合全局注册,举几个典型场景:
工具类纯函数:复用性极强
像时间格式化、金额格式化、数组去重这类纯函数(输入相同,输出一定相同,无副作用),每个组件都可能用到,全局注册后无需到处 import
。
比如电商项目里,把“分”转“元”的函数全局注册:
app.config.globalProperties.$formatPrice = (cent) => { return (cent / 100).toFixed(2) }
组件里调用 this.$formatPrice(1999)
直接得到 "19.99"
,省去重复写转换逻辑的麻烦。
权限控制函数:统一逻辑入口
后台管理系统里,判断用户有无某个按钮权限是高频操作,把权限判断逻辑全局化,组件里一行代码就能搞定。
假设用 Pinia 存用户权限,注册全局函数:
import { useUserStore } from './stores/user' <p>app.config.globalProperties.$hasPerm = (perm) => { const userStore = useUserStore() return userStore.permissions.includes(perm) }
组件里控制按钮显示:
<el-button v-if="$hasPerm('order:delete')" @click="deleteOrder">删除订单</el-button>
不用每个组件都 import useUserStore
再写判断,代码更简洁。
全局副作用操作:简化交互
有些全局交互(如弹框、通知),用全局函数调用更便捷,比如做一个全局 Toast 组件,注册 $showToast
函数:
app.config.globalProperties.$showToast = (msg) => { // 这里可以用组件库的Toast,比如Element Plus的ElMessage ElMessage.success(msg) }
组件里调用 this.$showToast('操作成功')
就能弹提示,无需每次导入 ElMessage
再调用。
第三方库封装:统一请求逻辑
如果项目用 axios 发请求,把 axios 封装成全局函数 $request
,统一处理请求拦截、错误处理,组件里调用更顺手。
import axios from 'axios' <p>const instance = axios.create({ baseURL: '/api', timeout: 5000 })</p> <p>// 请求拦截:加token instance.interceptors.request.use((config) => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = <code>Bearer ${token}</code> } return config })</p> <p>// 响应拦截:处理错误 instance.interceptors.response.use( (res) => res.data, (err) => { if (err.response.status === 401) { // 跳转到登录页 window.location.href = '/login' } return Promise.reject(err) } )</p> <p>app.config.globalProperties.$request = instance
组件里发请求只需:
this.$request.get('/user/list').then((res) => { ... })
反例:如果函数是某个页面独有的(比如订单页计算优惠金额),就别全局注册!否则全局作用域会越来越臃肿,后期维护时根本找不到函数定义在哪。
用全局函数容易踩哪些坑?怎么避?
全局函数看似方便,实则暗藏几个“陷阱”,提前避开,能少走很多弯路。
坑 1:命名冲突,函数“打架”
不同插件、不同模块都注册全局函数时,函数名重复就会覆盖,比如你注册了 $utils
,第三方插件也注册 $utils
,调用时根本不知道执行哪个。
解决方法:给全局函数加前缀,比如用项目名缩写 + 功能,像 myApp$formatDate
;或者团队内部定好命名规则(比如全局函数都以 $app
开头),从源头避免冲突。
坑 2:组合式组件里用 getCurrentInstance 不稳定
在 <script setup>
里用 getCurrentInstance
获取 proxy
调用全局函数,看似方便,但生产环境打包后可能失效(因为 tree - shaking 会把没用到的代码删掉,getCurrentInstance
可能被优化掉)。
解决方法:封装 composable 函数,把全局函数的调用逻辑藏在里面,比如写一个 useGlobal.js
:
// composables/useGlobal.js import { getCurrentInstance } from 'vue' <p>export function useGlobal() { const { proxy } = getCurrentInstance() return { $formatDate: proxy.$formatDate, $hasPerm: proxy.$hasPerm // 把需要的全局函数都列出来 } }
组件里用的时候,导入这个 composable:
<script setup> import { useGlobal } from '@/composables/useGlobal' <p>const { $formatDate } = useGlobal() const formatted = $formatDate('2024-09-15') </script>
这样做有两个好处:一是组件代码更简洁,不用直接操作 getCurrentInstance
;二是即使 getCurrentInstance
被优化,只需要改 useGlobal.js
里的逻辑,组件不用动。
坑 3:TypeScript 里没类型提示
如果项目用 TypeScript,直接写 this.$formatDate
时,IDE 不知道这个函数的参数、返回值是啥,容易写错。
解决方法:给全局函数加类型声明,在项目的 env.d.ts
(或 types/vue.d.ts
)里扩展 ComponentCustomProperties
接口:
declare module 'vue' { interface ComponentCustomProperties { // 给$formatDate加类型:参数是string|Date,返回string $formatDate: (date: string | Date) => string // 给$hasPerm加类型:参数是string,返回boolean $hasPerm: (perm: string) => boolean } } <p>export {}
这样 TS 就能识别这些全局函数的类型,写代码时不仅有自动提示,传错参数还会报错,避免低级错误。
坑 4:全局函数太多,维护成灾难
全局函数越积越多,后期想改逻辑时,根本找不到函数定义在哪,维护成本爆炸。
解决方法:集中管理全局函数,比如建一个 global - functions.js
文件,把所有全局函数的定义放在这里,再统一导入到 main.js
注册:
// global-functions.js export function formatDate(dateStr) { const date = new Date(dateStr) return `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}` } <p>export function hasPerm(perm) { const userStore = useUserStore() return userStore.permissions.includes(perm) }</p> <p>// main.js import { createApp } from 'vue' import App from './App.vue' import { formatDate, hasPerm } from './global-functions.js'</p> <p>const app = createApp(App) app.config.globalProperties.$formatDate = formatDate app.config.globalProperties.$hasPerm = hasPerm app.mount('#app')
这样所有全局函数的定义都在 global - functions.js
里,想改逻辑时直接找这个文件,清晰又高效。
和插件结合时,全局函数咋玩得更顺?
Vue 的插件机制(app.use(Plugin)
)适合把全局功能模块化封装,比如做一个 Toast 插件,里面不仅能注册全局函数,还能注册全局组件、指令,复用性拉满。
举个例子:写一个 Toast 插件,让所有组件能调用 $showToast
弹提示。
第一步:写插件逻辑
// plugins/toast.js export default { install(app) { // 注册全局函数$showToast app.config.globalProperties.$showToast = (message, duration = 2000) => { // 创建一个临时div显示Toast const toast = document.createElement('div') toast.innerText = message toast.style.position = 'fixed' toast.style.top = '20px' toast.style.left = '50%' toast.style.transform = 'translateX(-50%)' toast.style.padding = '8px 16px' toast.style.backgroundColor = 'rgba(0,0,0,0.8)' toast.style.color = '#fff' toast.style.borderRadius = '4px' document.body.appendChild(toast) <pre><code> // 定时移除Toast setTimeout(() => { toast.remove() }, duration) } // 也可以注册全局组件、指令(lt;Toast />组件) // app.component('Toast', ToastComponent) // app.directive('toast', toastDirective)
第二步:在 main.js 里使用插件
import { createApp } from 'vue' import App from './App.vue' import ToastPlugin from './plugins/toast.js' <p>const app = createApp(App) app.use(ToastPlugin) // 安装插件,自动注册全局函数、组件等 app.mount('#app')
第三步:
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。