uni-app 状态管理深度解析:Vuex 与全局方案实战指南
一、Vuex 使用示例
1. 基础 Vuex 配置
1.1 项目结构
src/
├── store/
│ ├── index.js # 主入口文件
│ └── modules/
│ └── counter.js # 计数器模块
└── main.js # 应用入口
1.2 安装 Vuex
bash
npm install vuex --save
2. 核心代码实现
2.1 主入口文件 (store/index.js)
javascript
import Vue from 'vue'
import Vuex from 'vuex'
import counter from './modules/counter'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
counter
}
})
2.2 计数器模块 (store/modules/counter.js)
javascript
export default {
state: {
count: 0
},
mutations: {
increment(state) {
state.count++
},
decrement(state) {
state.count--
},
setCount(state, value) {
state.count = value
}
},
actions: {
incrementAsync({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
}
},
getters: {
doubleCount: state => state.count * 2
}
}
2.3 应用入口 (main.js)
javascript
import Vue from 'vue'
import App from './App.vue'
import store from './store'
Vue.config.productionTip = false
new Vue({
store,
render: h => h(App)
}).$mount('#app')
3. 组件中使用示例
3.1 显示计数器 (CounterDisplay.vue)
html
<template>
<div>
<h2>计数器示例</h2>
<p>当前计数: {{ count }}</p>
<p>双倍计数: {{ doubleCount }}</p>
</div>
</template>
<script>
import { mapState, mapGetters } from 'vuex'
export default {
computed: {
...mapState('counter', ['count']),
...mapGetters('counter', ['doubleCount'])
}
}
</script>
3.2 操作计数器 (CounterControls.vue)
html
<template>
<div>
<button @click="increment">+1</button>
<button @click="decrement">-1</button>
<button @click="incrementAsync">1秒后+1</button>
<input type="number" v-model.number="newCount">
<button @click="setCount(newCount)">设置值</button>
</div>
</template>
<script>
import { mapMutations, mapActions } from 'vuex'
export default {
data() {
return {
newCount: 0
}
},
methods: {
...mapMutations('counter', ['increment', 'decrement', 'setCount']),
...mapActions('counter', ['incrementAsync'])
}
}
</script>
4. 完整应用示例 (App.vue)
html
<template>
<div id="app">
<CounterDisplay />
<CounterControls />
</div>
</template>
<script>
import CounterDisplay from './components/CounterDisplay.vue'
import CounterControls from './components/CounterControls.vue'
export default {
name: 'App',
components: {
CounterDisplay,
CounterControls
}
}
</script>
5. 核心概念说明
- State - 存储应用状态数据
javascript
state: {
count: 0
}
- Mutations - 修改状态的同步方法
javascript
mutations: {
increment(state) {
state.count++
}
}
- Actions - 可以包含异步操作
javascript
actions: {
incrementAsync({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
}
}
- Getters - 计算派生状态
javascript
getters: {
doubleCount: state => state.count * 2
}
- 模块化 - 按功能拆分模块
javascript
modules: {
counter
}
6. Vuex 中访问数据(state)方式
在 Vuex 中访问数据(state)主要有以下几种方式,取决于你是在组件内还是组件外访问:
1. 在 Vue 组件中访问 Vuex 数据
(1) 使用 this.$store
在 Vue 组件中,可以通过 this.$store
访问 Vuex 的 state、getters、mutations 和 actions:
javascript
// 访问 state
this.$store.state.carData
// 访问 getter
this.$store.getters.carDataGetter
// 调用 mutation
this.$store.commit('setCarData', payload)
// 调用 action
this.$store.dispatch('fetchCarData', payload)
(2) 使用 mapState
、mapGetters
、mapMutations
、mapActions
Vuex 提供了辅助函数,可以更方便地在组件中引入 Vuex 数据和方法:
javascript
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'
export default {
computed: {
// 映射 state.carData 到 this.carData
...mapState(['carData']),
// 映射 getters.carDataGetter 到 this.carDataGetter
...mapGetters(['carDataGetter']),
},
methods: {
// 映射 this.setCarData(payload) 到 this.$store.commit('setCarData', payload)
...mapMutations(['setCarData']),
// 映射 this.fetchCarData(payload) 到 this.$store.dispatch('fetchCarData', payload)
...mapActions(['fetchCarData']),
}
}
(3) 访问模块化的 Vuex 数据
如果使用了模块化(modules),访问方式稍有不同:
javascript
// 直接访问模块 state
this.$store.state.moduleName.carData
// 使用 mapState 访问模块 state
...mapState('moduleName', ['carData'])
// 使用命名空间访问 mutations/actions
this.$store.commit('moduleName/setCarData', payload)
this.$store.dispatch('moduleName/fetchCarData', payload)
2. 在非 Vue 组件(JS 文件)中访问 Vuex
如果你在普通的 JS 文件(如 API 请求、工具函数)中需要访问 Vuex,可以:
(1) 直接导入 store 实例
javascript
import store from '@/store' // 假设 store 导出在 @/store/index.js
// 访问 state
const carData = store.state.carData
// 调用 mutation
store.commit('setCarData', payload)
// 调用 action
store.dispatch('fetchCarData', payload)
(2) 动态获取 store(适用于插件或异步场景)
javascript
import Vue from 'vue'
// 获取全局 store
const store = Vue.prototype.$store
if (store) {
const carData = store.state.carData
}
3. 在 Vuex 内部访问数据
在 Vuex 的 getters
、mutations
、actions
内部,可以直接访问 state
和 getters
:
javascript
const store = new Vuex.Store({
state: {
carData: null,
},
getters: {
getCarData: (state) => state.carData,
},
mutations: {
setCarData(state, payload) {
state.carData = payload
},
},
actions: {
fetchCarData({ state, commit, getters }) {
const currentCarData = state.carData
const formattedData = getters.getCarData
commit('setCarData', newData)
},
},
})
总结
访问方式 | 适用场景 | 示例 |
---|---|---|
this.$store |
组件内直接访问 | this.$store.state.carData |
mapState /mapGetters |
组件内计算属性映射 | ...mapState(['carData']) |
mapMutations /mapActions |
组件内方法映射 | ...mapMutations(['setCarData']) |
直接导入 store |
非组件 JS 文件 | store.state.carData |
模块化访问 | 命名空间模块 | this.$store.state.moduleName.carData |
这样,你就可以在 Vue 项目的任何地方正确访问 Vuex 数据了! 🚀
二、全局方案灵活应用(轻量级方案)
2.1 全局变量深度应用
增强型全局数据管理
javascript
// app.js
class GlobalData {
constructor() {
this._config = {
apiBase: 'https://api.example.com',
theme: 'light'
}
}
get config() {
return {...this._config} // 返回副本保证只读
}
updateTheme(newTheme) {
this._config.theme = newTheme
uni.$emit('theme-change', newTheme)
}
}
App({
globalData: new GlobalData()
})
组件中安全访问
javascript
// 获取可维护的全局对象
const global = getApp().globalData
// 读取配置(推荐使用拷贝)
const currentTheme = {...global.config}.theme
// 修改时使用封装方法
global.updateTheme('dark')
2.2 事件通信高级技巧
安全通信模式
javascript
// 创建事件总线单例
const eventBus = new Vue()
// 封装安全监听方法
function safeOn(event, callback) {
const wrappedCallback = (...args) => {
try {
return callback(...args)
} catch (error) {
console.error(`事件处理错误: ${event}`, error)
}
}
eventBus.$on(event, wrappedCallback)
return () => eventBus.$off(event, wrappedCallback)
}
// 在组件中使用
export default {
mounted() {
this.unlisten = safeOn('data-updated', this.handleData)
},
beforeDestroy() {
this.unlisten && this.unlisten()
}
}
类型安全通信
javascript
// 创建事件类型枚举
const EventTypes = Object.freeze({
DATA_UPDATE: Symbol('DATA_UPDATE'),
USER_LOGOUT: Symbol('USER_LOGOUT')
})
// 发送规范事件
uni.$emit(EventTypes.DATA_UPDATE, {
timestamp: Date.now(),
payload: newData
})
// 接收时类型检查
uni.$on(EventTypes.DATA_UPDATE, ({ timestamp, payload }) => {
// 安全处理数据
})
三、方案选型决策树
是 是 是 否 否 否 是 父子组件 兄弟/跨级 高频 低频 需要管理状态吗? 数据需要跨多个组件吗? 需要长期保持状态吗? 使用Vuex+持久化 使用Vuex基础版 使用组件内状态 无需状态管理 需要组件通信吗? 组件层级关系如何? 使用props/$emit 通信频率如何? 使用Vuex 使用全局事件
四、性能优化实践
4.1 Vuex性能贴士
- 冻结大对象:防止Vue过度追踪
javascript
state: {
bigData: Object.freeze(largeStaticData)
}
- 模块懒加载:
javascript
const dynamicModule = () => import('./dynamic.module')
// 在需要时注册模块
store.registerModule('dynamic', dynamicModule)
4.2 全局事件优化
- 节流高频事件:
javascript
import throttle from 'lodash.throttle'
uni.$on('scroll-event', throttle(handleScroll, 100))
- 使用事件池:
javascript
const eventPool = new Map()
function registerEvent(event, callback) {
if (!eventPool.has(event)) {
eventPool.set(event, [])
}
eventPool.get(event).push(callback)
}
五、调试技巧大全
5.1 Vuex调试
javascript
// 打印mutation日志
store.subscribe((mutation, state) => {
console.groupCollapsed(`mutation ${mutation.type}`)
console.log('payload:', mutation.payload)
console.log('state:', state)
console.groupEnd()
})
5.2 事件追踪
javascript
// 监听所有事件
uni.$on('*', (event, ...args) => {
console.debug(`[Event Trace] ${event}`, args)
})
六、迁移策略建议
从全局变量迁移到Vuex
- 识别候选数据:找出被多个组件修改的全局变量
- 创建过渡层:
javascript
// 临时兼容方案
const legacyData = getApp().globalData.legacy
Object.defineProperty(Vue.prototype, '$legacy', {
get() {
console.warn('该属性已废弃,请使用store迁移')
return legacyData
}
})
- 逐步替换:按模块迁移数据到Vuex
七、最佳实践总结
-
核心准则:
- 表单数据保持本地
- 用户会话使用Vuex+持久化
- 页面间参数用URL传递
- 组件通信优先用事件
-
架构建议:
bashsrc/ ├── store/ │ ├── modules/ │ │ ├── user.store.js │ │ └── product.store.js │ └── index.js ├── utils/ │ └── eventBus.js └── config/ └── global.js
-
安全原则:
- Vuex mutation必须同步
- 全局变量只读访问
- 事件监听必须清理
- 敏感数据加密存储
通过本指南的实践,开发者可以构建出既具备企业级健壮性,又保持灵活性的uni-app应用架构。根据项目规模选择合适的方案,在保证可维护性的同时提升开发效率。