Code前端首页关于Code前端联系我们

Vue3 里的全局函数是啥?和 Vue2 有啥不一样?

terry 2周前 (10-03) 阅读数 46 #Vue
文章标签 Vue3 全局函数

不少刚接触 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(() =&gt; {
    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前端网发表,如需转载,请注明页面地址。

发表评论:

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

热门