1. 为什么需要全局公共函数?
在Vue3项目开发中,我们经常会遇到需要在多个组件中复用相同功能逻辑的情况。比如格式化日期、处理金额显示、校验手机号等工具函数。如果每个组件都单独引入这些函数,不仅会造成代码冗余,更会给后期维护带来困难。
全局公共函数的优势主要体现在三个方面:
- 一处定义,多处使用:避免重复代码,保持DRY原则
- 统一维护:修改时只需调整一处,所有使用的地方自动更新
- 提升开发效率:常用功能随取随用,无需反复导入
我在实际项目中就遇到过这样的案例:一个电商平台有超过30个组件需要使用相同的价格格式化函数。最初是在每个组件中单独实现,后来当需要调整价格显示规则时,不得不逐个修改,耗费了大量时间。采用全局公共函数方案后,这类问题就迎刃而解了。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Vue3中创建全局公共函数的三种方式
2.1 通过app.config.globalProperties挂载
这是Vue3官方推荐的方式,适合中小型项目使用。具体实现步骤如下:
javascript复制// utils/common.js
export const formatPrice = (price) => {
return '¥' + Number(price).toFixed(2)
}
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import * as commonUtils from './utils/common'
const app = createApp(App)
// 挂载全局方法
app.config.globalProperties.$utils = commonUtils
app.mount('#app')
使用时的注意事项:
- 方法名前建议加$前缀,避免与组件属性冲突
- 在模板中直接通过$utils访问
- 在setup中需要通过getCurrentInstance获取
vue复制<template>
<div>{{ $utils.formatPrice(99.9) }}</div>
</template>
<script setup>
import { getCurrentInstance } from 'vue'
const { proxy } = getCurrentInstance()
console.log(proxy.$utils.formatPrice(99.9))
</script>
2.2 使用provide/inject实现
这种方式更适合大型项目或需要按需使用的场景:
javascript复制// main.js
import { createApp } from 'vue'
import App from './App.vue'
import * as commonUtils from './utils/common'
const app = createApp(App)
// 提供全局方法
app.provide('$utils', commonUtils)
app.mount('#app')
在组件中使用:
vue复制<script setup>
import { inject } from 'vue'
const $utils = inject('$utils')
console.log($utils.formatPrice(99.9))
</script>
这种方式的优势在于:
- 显式声明依赖关系,代码更清晰
- 可以实现局部作用域的全局方法
- 更适合插件开发场景
2.3 组合式函数+自动导入方案
对于现代Vue3项目,结合Vite的自动导入功能,可以创建更优雅的方案:
javascript复制// composables/useGlobalUtils.js
export const useGlobalUtils = () => {
const formatPrice = (price) => {
return '¥' + Number(price).toFixed(2)
}
return { formatPrice }
}
配置vite.config.js:
javascript复制import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
export default defineConfig({
plugins: [
vue(),
AutoImport({
imports: [
{
'./composables/useGlobalUtils': [
['useGlobalUtils', '$utils']
]
}
]
})
]
})
这样在组件中就可以直接使用:
vue复制<script setup>
// 自动注入,无需手动导入
console.log($utils.formatPrice(99.9))
</script>
3. 全局公共函数的类型声明与TS支持
为了获得更好的TypeScript支持,我们需要为全局方法添加类型声明:
typescript复制// types/global.d.ts
import { CommonUtils } from '../utils/common'
declare module '@vue/runtime-core' {
interface ComponentCustomProperties {
$utils: typeof CommonUtils
}
}
对于provide/inject方式,可以这样声明:
typescript复制// types/global.d.ts
import { CommonUtils } from '../utils/common'
declare module '@vue/runtime-core' {
interface InjectionKey {
$utils: typeof CommonUtils
}
}
这样在使用时就能获得完整的类型提示和校验,避免运行时错误。
4. 实战中的最佳实践与避坑指南
4.1 全局函数的合理设计
根据我的项目经验,全局函数应该遵循以下原则:
- 保持纯函数特性:避免副作用,相同输入总是返回相同输出
- 功能单一:一个函数只做一件事
- 参数明确:避免使用arguments,使用具名参数
- 良好命名:动词开头,如formatDate、validateEmail
反例:
javascript复制// 不好的实现
app.config.globalProperties.$helper = {
handleData(data) {
// 既处理格式又发起请求
const formatted = data.map(...)
return axios.post('/api', formatted)
}
}
正例:
javascript复制// 好的实现
app.config.globalProperties.$utils = {
formatData(data) {
return data.map(...)
},
sendData(data) {
return axios.post('/api', data)
}
}
4.2 性能优化建议
全局函数虽然方便,但也要注意性能影响:
- 避免在全局函数中进行大量计算
- 对于高频操作,考虑使用防抖/节流
- 复杂数据处理可以考虑使用Web Worker
我曾经在一个项目中,全局格式化函数没有做性能优化,导致列表渲染时出现卡顿。后来通过缓存机制解决了这个问题:
javascript复制const formatCache = new Map()
export const formatPrice = (price) => {
if (formatCache.has(price)) {
return formatCache.get(price)
}
const result = '¥' + Number(price).toFixed(2)
formatCache.set(price, result)
return result
}
4.3 常见问题排查
问题1:方法未定义
- 检查main.js中是否正确挂载
- 确保组件渲染时app已经挂载完成
- 在setup中确认getCurrentInstance不为null
问题2:TypeScript报错
- 检查类型声明文件是否正确配置
- 确认类型声明文件被tsconfig包含
- 尝试重启IDE或重新生成类型定义
问题3:方法冲突
- 使用更具体的前缀,如$authUtils、$formatUtils
- 考虑使用Symbol作为provide的key
4.4 测试策略
全局函数应该被充分测试,建议:
- 为每个全局函数编写单元测试
- 测试边界条件和异常情况
- 在CI/CD流程中加入测试环节
示例测试代码:
javascript复制import { formatPrice } from '@/utils/common'
describe('formatPrice', () => {
it('should format number correctly', () => {
expect(formatPrice(10)).toBe('¥10.00')
expect(formatPrice('20.5')).toBe('¥20.50')
})
it('should handle invalid input', () => {
expect(formatPrice(null)).toBe('¥0.00')
expect(formatPrice(undefined)).toBe('¥0.00')
expect(formatPrice('abc')).toBe('¥NaN')
})
})
5. 高级应用场景
5.1 动态加载全局函数
对于大型项目,可以考虑按需加载全局函数:
javascript复制// main.js
const app = createApp(App)
// 动态加载工具函数
const loadUtils = async () => {
const utils = await import('./utils/common')
app.config.globalProperties.$utils = utils
}
loadUtils().then(() => {
app.mount('#app')
})
5.2 全局函数与插件开发
将全局函数封装为Vue插件,可以更好地复用:
javascript复制// plugins/utils.js
export default {
install(app, options) {
app.config.globalProperties.$utils = {
formatPrice(price) {
const symbol = options?.symbol || '¥'
return symbol + Number(price).toFixed(2)
}
}
}
}
// main.js
import UtilsPlugin from './plugins/utils'
app.use(UtilsPlugin, { symbol: '$' })
5.3 与Pinia/Vuex配合使用
全局函数可以与状态管理库协同工作:
javascript复制// stores/useUserStore.js
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
actions: {
async fetchUser() {
const data = await $utils.fetchApi('/user')
this.user = data
}
}
})
5.4 服务端渲染(SSR)适配
在SSR环境中使用全局函数需要注意:
javascript复制// 仅在客户端使用的函数
if (process.client) {
app.config.globalProperties.$browserUtils = {
getWindowSize() {
return {
width: window.innerWidth,
height: window.innerHeight
}
}
}
}
6. 项目结构建议
一个良好的全局函数项目结构应该清晰明了:
code复制src/
├── utils/
│ ├── common.js # 基础工具函数
│ ├── format.js # 格式化相关
│ ├── validate.js # 验证相关
│ └── index.js # 统一导出
├── composables/
│ ├── useGlobalUtils.js # 组合式函数
│ └── ...
├── types/
│ └── global.d.ts # 类型声明
└── main.js # 挂载入口
在index.js中统一导出:
javascript复制// utils/index.js
export * from './common'
export * from './format'
export * from './validate'
这样在main.js中只需引入一次:
javascript复制import * as utils from './utils'
app.config.globalProperties.$utils = utils
7. 版本升级与迁移策略
从Vue2迁移到Vue3时,全局函数的处理方式有所变化:
Vue2方式:
javascript复制Vue.prototype.$utils = utils
Vue3迁移步骤:
- 创建app实例
- 使用app.config.globalProperties替换Vue.prototype
- 更新组件中的使用方式
- 添加TypeScript支持
对于大型项目,建议逐步迁移:
- 先在新组件中使用新方案
- 逐步改造旧组件
- 最后移除Vue2的全局方法
8. 替代方案对比
除了全局函数,还有其他共享逻辑的方式:
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 全局函数 | 使用简单,随处可用 | 可能造成污染,难以追踪 | 简单工具函数 |
| 组合式函数 | 类型安全,组合性强 | 需要显式导入 | 复杂逻辑复用 |
| 插件 | 封装性好,可配置 | 开发成本较高 | 跨项目复用 |
| 工具类 | 面向对象,结构清晰 | 需要实例化 | 复杂工具集 |
| 静态方法 | 无需实例化 | 无法访问组件上下文 | 纯工具函数 |
根据项目规模和复杂度选择合适的方案。小型项目可以直接使用全局函数,大型项目建议采用组合式函数+自动导入的方案。
