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前端网发表,如需转载,请注明页面地址。
code前端网




发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。