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

Vue3 注册全局方法和 Vue2 有啥不同?

terry 2周前 (10-04) 阅读数 67 #Vue

在Vue3项目开发中,全局方法能帮我们把通用工具、跨组件逻辑统一管理,让代码更简洁,但Vue3和Vue2的全局方法玩法不一样,注册方式、使用场景甚至避坑要点都有变化,今天就通过问答形式,把Vue3全局方法的核心知识点拆明白,不管是新手入门还是项目优化,都能找到实用思路~

Vue2 里注册全局方法,是直接往 Vue.prototype 上挂属性,Vue.prototype.$axios = axios,所有组件实例都能通过 this.$axios 调用,但 Vue3 改用了“应用实例(app)”的方式,因为 Vue3 是通过 createApp 创建应用实例,全局配置都要挂在这个实例上。

Vue3 用 app.config.globalProperties 来注册全局方法,举个例子:

// Vue2 写法
import Vue from 'vue'
Vue.prototype.$formatTime = (time) => new Date(time).toLocaleString()
// Vue3 写法
import { createApp } from 'vue'
const app = createApp(App)
app.config.globalProperties.$formatTime = (time) => new Date(time).toLocaleString()
app.mount('#app')

背后的设计逻辑是 Vue3 更强调“树摇优化”“模块化”,通过应用实例隔离不同项目(比如一个页面多个Vue应用),全局方法不再和 Vue 构造函数强绑定,而是属于某个应用实例,这样多个应用共存时不会互相干扰。

Vue3 的组合式 API 里没有 this,所以在 <script setup>setup() 里用全局方法,得通过 getCurrentInstance() 拿到应用上下文,再取全局属性(后面会详细讲怎么用)。

注册全局方法有哪些常用方式?

全局方法的核心是“一处注册,多处复用”,常见场景分三类:工具函数、事件总线、第三方库挂载,下面逐个拆解:

通用工具函数挂载

项目里经常有时间格式化、权限判断、数据加密这类“无状态”工具,把它们挂到全局能避免重复写代码。

单函数注册:直接挂单个方法

// main.js
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
// 注册时间格式化函数
app.config.globalProperties.$formatTime = (time) => {
  return new Date(time).toLocaleDateString() // 只显示日期
}
app.mount('#app')

工具对象批量注册:把多个工具包成对象,避免全局变量过多

// main.js
const utils = {
  formatTime: (time) => new Date(time).toLocaleString(),
  formatMoney: (num) => num.toFixed(2).replace(/\d(?=(\d{3})+\.)/g, '$&,'), // 数字转千分位
  checkPerm: (perm) => { // 权限判断
    const userPerms = ['edit', 'delete'] // 假设从全局状态取权限
    return userPerms.includes(perm)
  }
}
app.config.globalProperties.$utils = utils

组件里怎么用?
在组合式 API 中,用 getCurrentInstance() 拿到应用上下文,再取全局属性:

<script setup>
import { getCurrentInstance } from 'vue'
const { appContext } = getCurrentInstance()
// 单函数调用
const $formatTime = appContext.config.globalProperties.$formatTime
// 工具对象调用
const $utils = appContext.config.globalProperties.$utils
// 使用示例
const formatedTime = $formatTime(Date.now())
const formatedMoney = $utils.formatMoney(123456)
const hasPerm = $utils.checkPerm('edit')
</script>

全局事件总线(跨组件通信)

Vue2 常用 EventBus(新建Vue实例当事件中心),Vue3 可以自己封装事件总线挂到全局,解决简单的跨组件通信(复杂场景优先用 Pinia/Vuex)。

封装事件总线并注册

// main.js
const eventBus = {
  events: {}, // 存事件和回调
  on(eventName, callback) {
    if (!this.events[eventName]) this.events[eventName] = []
    this.events[eventName].push(callback)
  },
  emit(eventName, ...args) {
    if (this.events[eventName]) {
      this.events[eventName].forEach(cb => cb(...args))
    }
  },
  off(eventName, callback) { // 可选:移除指定回调
    if (this.events[eventName]) {
      this.events[eventName] = this.events[eventName].filter(cb => cb !== callback)
    }
  }
}
app.config.globalProperties.$eventBus = eventBus

组件A(触发事件)

<script setup>
import { getCurrentInstance } from 'vue'
const { appContext } = getCurrentInstance()
const $eventBus = appContext.config.globalProperties.$eventBus
const handleClick = () => {
  $eventBus.emit('user-login', { token: 'xxx', user: { name: '小明' } })
}
</script>
<template>
  <button @click="handleClick">模拟登录</button>
</template>

组件B(监听事件)

<script setup>
import { onMounted, onUnmounted, getCurrentInstance } from 'vue'
const { appContext } = getCurrentInstance()
const $eventBus = appContext.config.globalProperties.$eventBus
onMounted(() => {
  $eventBus.on('user-login', (data) => {
    console.log('用户登录了:', data.user.name) // 打印“用户登录了:小明”
  })
})
onUnmounted(() => {
  // 组件销毁时移除事件,避免重复触发(内存泄漏)
  $eventBus.events['user-login'] = []
})
</script>

第三方库全局挂载

axioselement-plus 的消息提示(ElMessage)这类库,全局挂载后不用每次导入。

示例:全局挂载 axios

// main.js
import axios from 'axios'
app.config.globalProperties.$axios = axios.create({
  baseURL: 'https://api.example.com',
  timeout: 5000
})

组件里直接用

<script setup>
import { getCurrentInstance } from 'vue'
const { appContext } = getCurrentInstance()
const $axios = appContext.config.globalProperties.$axios
const fetchData = async () => {
  try {
    const res = await $axios.get('/user')
    console.log(res.data)
  } catch (err) {
    console.error(err)
  }
}
</script>

全局方法在实际项目中有哪些典型场景?

全局方法不是“为了用而用”,得结合真实需求,这三类场景用全局方法能大幅提效:

多组件复用的无状态工具

比如电商项目里,商品价格格式化(分转元、加千分位)、订单时间解析(后台返回时间戳转“多久前”)、权限按钮判断(比如按钮级权限控制,多个页面的按钮都要判断是否显示)。

把这些工具集中到全局后,每个组件不用再单独写逻辑或导入工具文件,直接调 $utils.formatPrice(1234) 就能得到“¥12.34”。

全局初始化与配置

项目启动时要设置主题(亮色/暗色)、语言(中文/英文)、请求拦截器(统一加token),这些逻辑可以封装成全局方法,在 main.js 里执行后,所有组件随时能获取配置。

比如主题切换:

// main.js
let currentTheme = 'light'
const setTheme = (theme) => {
  currentTheme = theme
  document.documentElement.setAttribute('data-theme', theme)
}
app.config.globalProperties.$theme = {
  set: setTheme,
  get: () => currentTheme
}

组件里切换主题:

<script setup>
import { getCurrentInstance } from 'vue'
const { appContext } = getCurrentInstance()
const $theme = appContext.config.globalProperties.$theme
const switchTheme = () => {
  const newTheme = $theme.get() === 'light' ? 'dark' : 'light'
  $theme.set(newTheme)
}
</script>
<template>
  <button @click="switchTheme">切换主题</button>
</template>

简化复杂逻辑的调用

比如文件上传组件,多个页面都要上传图片,但上传逻辑(分片、断点续传、进度条)很复杂,把上传逻辑封装成全局方法 $uploadFile,组件里只需传文件和回调,不用关心内部实现。

// main.js 封装上传逻辑
const uploadFile = async (file, onProgress) => {
  // 分片、请求等复杂逻辑
  // 调用onProgress更新进度
}
app.config.globalProperties.$uploadFile = uploadFile

组件里调用:

<script setup>
import { getCurrentInstance } from 'vue'
const { appContext } = getCurrentInstance()
const $uploadFile = appContext.config.globalProperties.$uploadFile
const handleUpload = async (file) => {
  await $uploadFile(file, (percent) => {
    console.log('上传进度:', percent)
  })
}
</script>

使用全局方法要避开哪些坑?

全局方法虽方便,但用不好会导致代码难维护、冲突甚至内存泄漏,这几个“雷区”要避开:

命名冲突风险

如果多个插件或团队成员都往全局挂方法,很容易重名(比如都叫 $utils)。解决方案:加项目前缀,比如公司名缩写 $myApp_utils,或约定团队内的命名规则(如业务模块+功能,$order_formatTime)。

TypeScript 类型丢失(TS项目必看)

Vue3 + TS 项目中,直接用 $formatTime 会报“属性不存在”的错误,因为TS不知道全局属性的类型。解决方法:在类型声明文件(如 env.d.ts)里扩展类型:

// env.d.ts
import { App } from 'vue'
declare module '@vue/runtime-core' {
  interface ComponentCustomProperties {
    // 单个方法类型
    $formatTime: (time: string | number | Date) => string
    // 工具对象类型
    $utils: {
      formatTime: (time: string | number | Date) => string
      formatMoney: (num: number) => string
      checkPerm: (perm: string) => boolean
    }
    // 事件总线类型
    $eventBus: {
      on: (eventName: string, callback: (...args: any[]) => void) => void
      emit: (eventName: string, ...args: any[]) => void
    }
  }
}

这样写后,TS 能自动推断全局方法的参数和返回值,IDE 也会有代码提示。

内存泄漏(事件总线必踩)

用全局事件总线时,组件销毁后如果没移除事件监听,下次触发事件会重复执行回调,甚至导致内存溢出。解决方法:组件销毁时(onUnmounted)主动清空事件回调。

比如之前的事件总线示例,组件B销毁时要清空:

onUnmounted(() => {
  $eventBus.events['user-login'] = [] // 清空该事件的所有回调
})

如果是单个回调移除,也可以给 off 方法传回调函数:

// 事件总线加off方法
off(eventName, callback) {
  if (this.events[eventName]) {
    this.events[eventName] = this.events[eventName].filter(cb => cb !== callback)
  }
}
// 组件里存回调函数,销毁时移除
const handleLogin = (data) => { ... }
$eventBus.on('user-login', handleLogin)
onUnmounted(() => {
  $eventBus.off('user-login', handleLogin)
})

过度依赖全局方法(逻辑耦合风险)

全局方法适合无状态、纯工具类的逻辑,如果是有状态的业务逻辑(比如用户信息、购物车数据),优先用 Pinia 或 Vuex 管理,因为全局方法里维护状态容易变成“全局变量大乱斗”,后期很难追溯状态变化。

有没有更优雅的全局方法管理方案?

如果项目里全局方法很多,或需要多人协作维护,插件化 + provide/inject 是更优雅的方案,把全局方法封装成 Vue 插件,既解耦又方便复用。

步骤1:封装插件(以工具函数为例)

// plugins/utils-plugin.js
export default {
  install(app) {
    // 工具函数定义
    const formatTime = (time) => new Date(time).toLocaleString()
    const formatMoney = (num) => num.toFixed(2).replace(/\d(?=(\d{3})+\.)/g, '$&,')
    const checkPerm = (perm) => { /* 权限逻辑 */ }
    // 挂载到全局属性
    app.config.globalProperties.$formatTime = formatTime
    app.config.globalProperties.$formatMoney = formatMoney
    app.config.globalProperties.$checkPerm = checkPerm
    // 同时提供provide,方便inject使用
    app.provide('utils', { formatTime, formatMoney, checkPerm })
  }
}

步骤2:在main.js里注册插件

import { createApp } from 'vue'
import App from './App.vue'
import UtilsPlugin from './plugins/utils-plugin.js'
const app = createApp(App)
app.use(UtilsPlugin) // 注册插件
app.mount('#app')

步骤3:组件里两种方式调用

方式1:全局属性(getCurrentInstance)
和之前用法一样,适合非 <script setup> 或需要灵活获取的场景。

方式2:provide/inject
更推荐用 inject,因为组合式 API 中 inject 更符合“依赖注入”的设计,且代码更简洁:

<script setup>
import { inject } from 'vue'
// 注入工具对象
const utils = inject('utils')
// 使用
const formatedTime = utils.formatTime(Date.now())
const formatedMoney = utils.formatMoney(1234)
</script>

插件化的优势

  • 解耦性强:每个插件负责一块功能(工具、权限、请求),代码拆分更清晰。
  • 复用性高:其他项目要用到相同工具,直接复制插件文件即可。
  • 类型友好:插件里的类型声明可以内聚,TS 支持更自然。

Vue3全局方法的核心思路

Vue3 全局方法的本质是“应用级的共享能力”,核心要把握这几点:

  • 注册方式:通过 app.config.globalProperties 挂载,替代 Vue2 的 Vue.prototype
  • 使用场景:无状态工具、简单跨组件通信、第三方库封装。
  • 避坑关键:命名规范、TS 类型、内存泄漏、状态逻辑分离。
  • 进阶方案:插件化 + provide/inject 让全局方法更易维护。

实际开发中,要根据项目规模选择方案:小项目直接挂全局属性快速开发;中大型项目用插件化 + 状态管理工具,兼顾效率和可维护性。

最后提醒:全局方法是“锦上添花”的工具,别为了用而用,先想清楚逻辑是否真的通用,再决定是否全局化——合理使用才能让代码既简洁又好维护~

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

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

热门