vue + typescript,定义全局变量或者方法
vue + typescript,定义全局变量或者方法
一、背景与问题
在Vue 3 + TypeScript项目中,开发者常常需要定义一些全局可用的变量或方法。这类需求可能出现在:
- 需要跨组件共享的配置信息(如API基础地址、用户权限等)
- 需要全局访问的工具函数(如格式化函数、验证函数等)
- 需要统一管理的全局状态(如主题色、语言切换等)
传统的解决方案通常有两种:使用Vue的app.config.globalProperties或通过全局状态管理模式(如Vuex/Pinia)。但这些方案在TypeScript项目中存在显著差异,需要深入理解其工作原理和适用场景。
二、基本原理
1. Vue全局属性机制
Vue 3通过app.config.globalProperties暴露全局属性,其本质是通过Proxy实现的动态属性访问。当访问this.xxx时,会自动查找全局属性。
// src/main.ts
const app = createApp(App)
app.config.globalProperties.$formatDate = (date: Date) => {
return date.toLocaleDateString()
}
app.mount('#app')2. 状态管理模式
Vuex和Pinia通过创建全局的store实例,利用Vue的响应式系统实现状态共享。其核心原理是通过ref或reactive创建响应式数据,并通过mapState等辅助函数在组件中使用。
三、环境准备
确保项目已初始化:
npm init -y
npm install vue@next typescript @vue/compiler-sfc --save
npx create-vue@latest在tsconfig.json中添加以下配置:
{
"compilerOptions": {
"moduleResolution": "node",
"module": "ESNext",
"target": "ESNext",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"esModuleInterop": true,
"moduleResolution": "node",
"baseUrl": ".",
"types": ["vue", "node"]
}
}四、核心实现
1. 全局变量定义(推荐方案)
// src/global.ts
export const globalConfig = {
API_BASE_URL: 'https://api.example.com',
VERSION: '1.0.0'
}
export function formatTime(date: Date): string {
return date.toLocaleTimeString()
}// src/main.ts
import { createApp } from 'vue'
import App from './App.vue'
import { globalConfig, formatTime } from './global'
const app = createApp(App)
app.config.globalProperties.$config = globalConfig
app.config.globalProperties.$formatTime = formatTime
app.mount('#app')<!-- src/App.vue -->
<template>
<div>
<p>当前版本: {{ $config.VERSION }}</p>
<p>当前时间: {{ $formatTime(new Date()) }}</p>
</div>
</template>关键点:
- 使用
globalProperties时需注意类型定义 - 不推荐直接暴露对象,建议通过工厂函数封装
- 避免在全局对象中混杂业务逻辑
2. 使用Vuex(传统方案)
// src/store/index.ts
import { createStore } from 'vuex'
interface State {
theme: string
darkMode: boolean
}
const store = createStore<State>({
state: {
theme: 'light',
darkMode: false
},
mutations: {
setTheme(state, theme: string) {
state.theme = theme
},
toggleDarkMode(state) {
state.darkMode = !state.darkMode
}
}
})
export default store// src/main.ts
import { createApp } from 'vue'
import App from './App.vue'
import store from './store'
const app = createApp(App)
app.use(store)
app.mount('#app')<!-- src/App.vue -->
<template>
<div :class="darkMode ? 'dark' : ''">
<p>当前主题: {{ theme }}</p>
<button @click="toggleDarkMode">切换模式</button>
</div>
</template>
<script lang="ts">
import { mapState, mapMutations } from 'vuex'
export default {
computed: {
...mapState(['theme', 'darkMode'])
},
methods: {
...mapMutations(['toggleDarkMode'])
}
}
</script>3. 使用Pinia(现代方案)
// src/stores/global.ts
import { defineStore } from 'pinia'
export const useGlobalStore = defineStore('global', {
state: () => ({
theme: 'light',
darkMode: false
}),
actions: {
setTheme(theme: string) {
this.theme = theme
},
toggleDarkMode() {
this.darkMode = !this.darkMode
}
}
})// src/main.ts
import { createApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia'
const app = createApp(App)
app.use(createPinia())
app.mount('#app')<!-- src/App.vue -->
<template>
<div :class="darkMode ? 'dark' : ''">
<p>当前主题: {{ theme }}</p>
<button @click="toggleDarkMode">切换模式</button>
</div>
</template>
<script lang="ts">
import { useGlobalStore } from '@/stores/global'
export default {
setup() {
const globalStore = useGlobalStore()
return {
theme: globalStore.theme,
darkMode: globalStore.darkMode,
toggleDarkMode: globalStore.toggleDarkMode
}
}
}
</script>五、完整案例
创建一个包含全局配置、工具函数和状态管理的完整案例:
// src/global.ts
export const globalConfig = {
API_BASE_URL: 'https://api.example.com',
VERSION: '1.0.0'
}
export function formatTime(date: Date): string {
return date.toLocaleTimeString()
}
export function fetchWithAuth(url: string, data: Record<string, any> = {}) {
return fetch(`${globalConfig.API_BASE_URL}${url}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('token')}`
},
body: JSON.stringify(data)
})
}// src/store/global.ts
import { defineStore } from 'pinia'
export const useGlobalStore = defineStore('global', {
state: () => ({
theme: 'light',
darkMode: false,
user: {
id: 0,
name: 'Guest'
}
}),
actions: {
setTheme(theme: string) {
this.theme = theme
},
toggleDarkMode() {
this.darkMode = !this.darkMode
},
setUser(user: Record<string, any>) {
this.user = user
}
}
})// src/main.ts
import { createApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia'
import { useGlobalStore } from './store/global'
import { globalConfig, formatTime, fetchWithAuth } from './global'
const app = createApp(App)
app.use(createPinia())
app.config.globalProperties.$config = globalConfig
app.config.globalProperties.$formatTime = formatTime
app.config.globalProperties.$fetchWithAuth = fetchWithAuth
app.mount('#app')<!-- src/App.vue -->
<template>
<div :class="darkMode ? 'dark' : ''">
<header>
<h1>全局状态管理示例</h1>
<p>当前版本: {{ $config.VERSION }}</p>
<p>当前时间: {{ $formatTime(new Date()) }}</p>
<p>当前主题: {{ theme }}</p>
</header>
<main>
<section>
<h2>用户信息</h2>
<p>用户ID: {{ user.id }}</p>
<p>用户名: {{ user.name }}</p>
</section>
<section>
<h2>API测试</h2>
<button @click="fetchData">获取数据</button>
<p v-if="response">{{ response }}</p>
</section>
</main>
<footer>
<button @click="toggleDarkMode">切换模式</button>
</footer>
</div>
</template>
<script lang="ts">
import { useGlobalStore } from '@/store/global'
export default {
setup() {
const globalStore = useGlobalStore()
const { theme, darkMode, user, toggleDarkMode } = globalStore
const fetchData = async () => {
try {
const response = await globalStore.$fetchWithAuth('/api/data', {
page: 1
})
if (response.ok) {
const data = await response.json()
globalStore.setUser(data.user)
return data.message
}
return '请求失败'
} catch (error) {
return '网络错误'
}
}
return {
theme,
darkMode,
user,
toggleDarkMode,
fetchData
}
}
}
</script>六、源码解析
createPinia()创建Pinia实例,通过app.use()注册到Vue实例defineStore创建的store实例包含state和actions,通过useGlobalStore()在组件中使用globalProperties暴露的全局方法在组件中通过this.$xxx访问fetchWithAuth函数使用全局配置进行API请求,避免硬编码
七、进阶使用
1. 类型增强
// src/global.ts
export interface GlobalConfig {
API_BASE_URL: string
VERSION: string
}
export const globalConfig: GlobalConfig = {
API_BASE_URL: 'https://api.example.com',
VERSION: '1.0.0'
}2. 模块化状态管理
// src/stores/user.ts
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({
id: 0,
name: 'Guest'
}),
actions: {
updateProfile(data: Record<string, any>) {
this.id = data.id
this.name = data.name
}
}
})3. 响应式数据共享
// src/stores/shared.ts
import { defineStore } from 'pinia'
export const useSharedStore = defineStore('shared', {
state: () => ({
loading: false,
error: null as string | null
}),
actions: {
setLoading(value: boolean) {
this.loading = value
},
setError(value: string | null) {
this.error = value
}
}
})八、性能与工程实践
1. 性能优化
- 避免在全局对象中存储大量数据
- 使用
computed处理复杂计算 - 对频繁更新的状态使用
watch进行优化 - 使用
shouldUpdate控制响应式更新
2. 异常处理
// 全局错误处理
window.onerror = (message, source, lineno, colno, error) => {
console.error('全局错误:', {
message,
source,
lineno,
colno,
error
})
return true
}3. 安全考虑
- 对全局方法进行权限校验
- 使用
tsconfig.json的strict模式避免类型错误 - 对敏感数据进行加密处理
- 设置
Content-Security-Policy头防止XSS攻击
九、常见问题与踩坑
1. 全局变量未初始化
// 错误示例
app.config.globalProperties.$formatTime = (date: Date) => {
return date.toLocaleTimeString()
}问题:未在main.ts中正确注册
解决:确保在创建Vue实例后注册全局属性
2. 状态更新不生效
// 错误示例
this.$config.theme = 'dark'问题:直接修改不可变对象的属性
解决:通过工厂函数或响应式方法更新
this.$config = { ...this.$config, theme: 'dark' }3. 全局状态污染
问题:多个组件直接修改同一全局对象
解决:使用Pinia的state管理,通过actions进行状态更新
十、最佳实践
- 优先使用Pinia:对于需要响应式更新和模块化管理的场景
- 谨慎使用全局变量:仅用于少量、简单的配置信息
- 类型定义:为所有全局对象和方法提供严格类型定义
- 封装工具函数:避免直接暴露函数,通过工厂函数进行封装
- 模块化管理:将相关功能组织到独立的store文件中
- 避免全局状态:在组件间使用props和events进行数据传递
十一、总结
在Vue 3 + TypeScript项目中定义全局变量或方法时,需要根据具体场景选择合适的方案。对于简单的配置信息,使用globalProperties是最直接的方式;对于需要响应式更新和复杂状态管理的场景,推荐使用Pinia。需要注意避免全局状态污染,合理使用类型定义,确保代码的可维护性和可扩展性。在实际开发中,应根据项目规模、团队习惯和功能复杂度选择最合适的方案,避免过度设计或使用不当导致的维护困难。
评论已关闭