模式切换
全局状态 vs 局部状态
在 Vue 应用开发中,合理划分全局状态和局部状态是架构设计的关键决策。以下是两者的深度对比和最佳实践指南。
核心概念对比
维度 | 全局状态 | 局部状态 |
---|---|---|
作用范围 | 整个应用共享 | 单个组件或少数紧密耦合组件 |
生命周期 | 应用生命周期 | 组件生命周期 |
典型场景 | 用户信息、主题、权限 | 表单数据、UI控制状态 |
管理工具 | Pinia/Vuex | ref /reactive /组件props |
可预测性 | 需严格管理变更 | 变更影响范围小 |
全局状态详解
定义与特点
全局状态是多个无关组件需要访问和修改的数据,具有以下特征:
- 跨路由/组件共享
- 需要持久化或同步
- 有明确的变更规则
典型使用场景
- 用户认证信息(token、用户资料)
- 应用主题/语言偏好
- 全局通知/弹窗状态
- 购物车数据(电商场景)
- 多组件共享的API数据
实现方案
方案1:Pinia(推荐)
javascript
// stores/user.js
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({
token: localStorage.getItem('token') || '',
profile: null
}),
actions: {
async login(credentials) {
const res = await api.login(credentials)
this.token = res.token
this.profile = res.user
localStorage.setItem('token', res.token)
}
}
})
方案2:Vuex
javascript
// store/modules/user.js
export default {
namespaced: true,
state: () => ({
token: ''
}),
mutations: {
SET_TOKEN(state, token) {
state.token = token
}
}
}
管理原则
- 单一数据源:每个业务域只有一个store
- 不可变更新:通过actions修改状态(Pinia允许直接修改但不推荐)
- 类型安全:为全局状态定义TypeScript接口
- 持久化策略:关键状态需配合localStorage/IndexedDB
局部状态详解
定义与特点
局部状态是组件自身或父子组件间使用的数据,特点包括:
- 影响范围有限
- 随组件销毁而释放
- 修改无需复杂流程
典型使用场景
- 表单输入框的值
- 组件展开/折叠状态
- 对话框可见性控制
- 列表排序/筛选条件
- 动画过渡状态
实现方案
方案1:组合式API(推荐)
vue
<script setup>
import { ref } from 'vue'
// 局部状态
const searchQuery = ref('')
const isDropdownOpen = ref(false)
function toggleDropdown() {
isDropdownOpen.value = !isDropdownOpen.value
}
</script>
方案2:选项式API
javascript
export default {
data() {
return {
searchQuery: '',
isDropdownOpen: false
}
},
methods: {
toggleDropdown() {
this.isDropdownOpen = !this.isDropdownOpen
}
}
}
管理原则
- 就近原则:状态定义在最近共同祖先组件
- 最小暴露:只向子组件暴露必要状态
- 响应式优化:复杂状态使用
shallowRef
/shallowReactive
- 清理副作用:
onUnmounted
中取消事件监听
决策流程图
混合使用模式
全局状态派生局部状态
vue
<script setup>
import { useUserStore } from '@/stores/user'
import { computed } from 'vue'
const userStore = useUserStore()
// 从全局状态派生局部状态
const username = computed(() => userStore.profile?.name || '游客')
</script>
局部状态同步到全局
javascript
// 在组件中
const formData = ref({})
const userStore = useUserStore()
const submit = () => {
// 将局部状态提交到全局
userStore.updateProfile(formData.value)
}
常见误区与解决方案
误区 | 问题 | 解决方案 |
---|---|---|
过度全局化 | 组件复用困难 | 严格评估状态使用范围 |
深层嵌套props | "Prop Drilling"问题 | 使用provide/inject |
直接修改全局状态 | 难以追踪变更 | 强制通过actions修改 |
忽略状态清理 | 内存泄漏 | 使用onUnmounted 生命周期 |
性能优化技巧
全局状态优化
- 使用
computed
延迟计算 - 大对象分片存储(如分页数据)
- 防抖高频更新操作
局部状态优化
- 非响应式数据使用
markRaw
- 列表项使用
v-memo
- 高频更新使用
shallowRef
实战案例
全局主题切换
javascript
// stores/theme.js
export const useThemeStore = defineStore('theme', {
state: () => ({
mode: 'light',
colors: { primary: '#409EFF' }
}),
actions: {
toggleMode() {
this.mode = this.mode === 'light' ? 'dark' : 'light'
document.body.className = this.mode
}
}
})
局部表单状态
vue
<script setup>
const form = reactive({
name: '',
age: null,
agree: false
})
const validate = () => {
if (!form.name) alert('姓名必填')
}
</script>
<template>
<input v-model="form.name">
<button @click="validate">提交</button>
</template>