数据共享的艺术
UniApp全局状态管理宝典:数据共享的艺术
为什么需要全局状态管理?
想象你的应用是一个繁忙的公司,不同部门(组件)需要共享和更新同一份文件(数据)。如果没有集中管理,就会出现数据混乱、版本冲突和沟通困难的问题。
┌──────────────────────────────────────────────────────┐
│ 没有全局状态管理的混乱 │
│ │
│ 组件A 组件B 组件C 组件D │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │状态A│ │状态B│ │状态C│ │状态D│ │
│ └─────┘ └─────┘ └─────┘ └─────┘ │
│ ↓ ↑ ↓ ↑ │
│ └────────────┘ └────────────┘ │
│ 混乱的数据同步 │
│ │
└──────────────────────────────────────────────────────┘
UniApp全局状态管理的五大方式
1. Vuex/Pinia:正规军的数据管理中心
Vuex和Pinia就像一个中央档案馆,所有数据变更都必须登记备案,并由专人(mutations/actions)处理,确保数据的安全与一致。
生活类比:中央银行系统
Vuex就像一个国家的中央银行系统:
- 金库(state) - 集中存储所有资金
- 出纳员(mutations) - 负责资金存取,记录每笔交易
- 银行经理(actions) - 处理复杂业务,可能涉及多项交易
- 会计(getters) - 计算资产状况,生成财务报表
- 分行网点(modules) - 处理不同区域或业务线的独立账务
┌──────────────────────────────────────────────────────┐
│ Vuex数据流向图 │
│ │
│ ┌───────────┐ ┌───────────┐ │
│ │ 视图 │──────►│ Actions │ │
│ │ Components│ └─────┬─────┘ │
│ └─────┬─────┘ │ │
│ │ ▼ │
│ │ ┌───────────┐ │
│ │ │ Mutations │ │
│ │ └─────┬─────┘ │
│ │ │ │
│ │ ▼ │
│ │ ┌───────────┐ ┌──────────┐ │
│ │◄────────────│ State │◄───│ Getters │ │
│ │ └───────────┘ └──────────┘ │
│ │ ▲ │
│ └───────────────────┘ │
│ │
└──────────────────────────────────────────────────────┘
代码示例:设置Vuex
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'Vue.use(Vuex)export default new Vuex.Store({state: {// 用户信息userInfo: null,// 购物车cartItems: [],// 应用设置appSettings: {theme: 'light',fontSize: 'medium'}},getters: {// 计算购物车总金额cartTotal(state) {return state.cartItems.reduce((total, item) => {return total + (item.price * item.quantity)}, 0)},// 判断用户是否登录isLoggedIn(state) {return state.userInfo !== null}},mutations: {// 更新用户信息SET_USER_INFO(state, userInfo) {state.userInfo = userInfo},// 添加商品到购物车ADD_TO_CART(state, product) {const existItem = state.cartItems.find(item => item.id === product.id)if (existItem) {existItem.quantity++} else {state.cartItems.push({...product,quantity: 1})}},// 更新应用设置UPDATE_SETTINGS(state, settings) {state.appSettings = {...state.appSettings,...settings}}},actions: {// 异步登录操作async login({ commit }, credentials) {try {// 调用登录APIconst response = await uni.request({url: 'https://api.example.com/login',method: 'POST',data: credentials})// 保存用户信息commit('SET_USER_INFO', response.data.userInfo)// 存储tokenuni.setStorageSync('token', response.data.token)return response.data} catch (error) {console.error('登录失败', error)throw error}},// 异步加载购物车async loadCart({ commit }) {try {const token = uni.getStorageSync('token')if (!token) returnconst response = await uni.request({url: 'https://api.example.com/cart',header: { Authorization: `Bearer ${token}` }})// 更新购物车response.data.items.forEach(item => {commit('ADD_TO_CART', item)})} catch (error) {console.error('加载购物车失败', error)}}},// 分模块管理不同功能的状态modules: {products: {namespaced: true,state: {list: [],categories: []},mutations: {SET_PRODUCTS(state, products) {state.list = products}}}}
})
使用Vuex的组件示例
<template><view class="container"><!-- 显示用户信息 --><view class="user-info" v-if="isLoggedIn"><text>欢迎回来,{{ userInfo.nickname }}</text></view><button v-else @tap="handleLogin">登录</button><!-- 购物车信息 --><view class="cart-info"><text>购物车: {{ cartItems.length }}件商品</text><text>总计: ¥{{ cartTotal }}</text></view><!-- 主题设置 --><view class="settings"><text>当前主题: {{ appSettings.theme }}</text><button @tap="toggleTheme">切换主题</button></view></view>
</template><script>
import { mapState, mapGetters, mapActions } from 'vuex'export default {computed: {// 映射state到计算属性...mapState(['userInfo', 'cartItems', 'appSettings']),// 映射getters到计算属性...mapGetters(['isLoggedIn', 'cartTotal'])},methods: {// 映射actions到方法...mapActions(['login', 'loadCart']),async handleLogin() {try {await this.login({username: 'testuser',password: 'password123'})uni.showToast({title: '登录成功'})// 登录成功后加载购物车this.loadCart()} catch (error) {uni.showToast({title: '登录失败',icon: 'none'})}},toggleTheme() {// 切换主题const newTheme = this.appSettings.theme === 'light' ? 'dark' : 'light'this.$store.commit('UPDATE_SETTINGS', { theme: newTheme })}},onLoad() {// 页面加载时检查用户状态if (uni.getStorageSync('token')) {this.loadCart()}}
}
</script>
2. Uni.storage:简单而持久的数据仓库
当需要在应用重启后仍保留数据状态时,可以使用uni.storage API将数据存储在本地。
生活类比:保险柜
Uni.storage就像你家的保险柜:
- 适合存放重要但不常取用的物品(数据)
- 即使停电(应用关闭),物品也安全保存
- 取放操作较慢,不适合频繁操作
- 空间有限,不能存放过大的物品
┌──────────────────────────────────────────────────────┐
│ Storage工作流程 │
│ │
│ ┌───────────┐ 存储 ┌───────────┐ │
│ │ 应用数据 │─────────►│本地存储区 │ │
│ │ │◄─────────│ │ │
│ └───────────┘ 读取 └───────────┘ │
│ │ │ │
│ │ │ │
│ ▼ ▼ │
│ ┌───────────┐ ┌───────────┐ │
│ │ 应用关闭 │ │ 设备重启 │ │
│ └───────────┘ └───────────┘ │
│ │ │ │
│ │ │ │
│ ▼ ▼ │
│ └──────────数据依然保留──────────┘ │
│ │
└──────────────────────────────────────────────────────┘
代码示例:封装Storage状态管理器
// utils/storageManager.js
class StorageManager {constructor(namespace = 'app') {this.namespace = namespacethis.memoryState = this.loadState()// 监听应用退出时保存状态// #ifdef APP-PLUSplus.runtime.addEventListener('quit', () => {this.saveState()})// #endif}// 加载状态loadState() {try {const stateJSON = uni.getStorageSync(`${this.namespace}_state`)return stateJSON ? JSON.parse(stateJSON) : this.getInitialState()} catch (error) {console.error('加载状态失败', error)return this.getInitialState()}}// 保存状态saveState() {try {uni.setStorageSync(`${this.namespace}_state`, JSON.stringify(this.memoryState))} catch (error) {console.error('保存状态失败', error)}}// 初始状态getInitialState() {return {userInfo: null,settings: {theme: 'light',language: 'zh-CN'},history: [],favorites: []}}// 获取整个状态getState() {return this.memoryState}// 获取特定键的值get(key) {return key.split('.').reduce((obj, k) => {return obj && obj[k] !== undefined ? obj[k] : null}, this.memoryState)}// 设置特定键的值set(key, value) {const keys = key.split('.')const lastKey = keys.pop()const target = keys.reduce((obj, k) => {if (obj[k] === undefined) obj[k] = {}return obj[k]}, this.memoryState)target[lastKey] = valuethis.saveState() // 保存到存储return value}// 特定功能方法setUserInfo(userInfo) {this.set('userInfo', userInfo)}addToFavorites(item) {const favorites = this.get('favorites') || []if (!favorites.some(i => i.id === item.id)) {favorites.push(item)this.set('favorites', favorites)}}removeFromFavorites(itemId) {let favorites = this.get('favorites') || []favorites = favorites.filter(item => item.id !== itemId)this.set('favorites', favorites)}// 清除所有状态clearState() {this.memoryState = this.getInitialState()this.saveState()}
}// 创建并导出单例实例
export const stateManager = new StorageManager()
使用StorageManager的组件示例
<template><view class="settings-page"><view class="setting-item"><text>主题</text><picker :value="themeIndex" :range="themes" @change="changeTheme"><view class="picker-value">{{ themes[themeIndex] }}</view></picker></view><view class="setting-item"><text>语言</text><picker :value="languageIndex" :range="languages" range-key="name"@change="changeLanguage"><view class="picker-value">{{ languages[languageIndex].name }}</view></picker></view><view class="favorites"><text class="section-title">我的收藏 ({{ favorites.length }})</text><view v-for="(item, index) in favorites" :key="index"class="favorite-item"><text>{{ item.name }}</text><button @tap="removeFavorite(item.id)" size="mini" type="warn">删除</button></view></view><button @tap="clearAll" type="warn">清除所有数据</button></view>
</template><script>
import { stateManager } from '@/utils/storageManager.js'export default {data() {return {themes: ['light', 'dark', 'auto'],themeIndex: 0,languages: [{ code: 'zh-CN', name: '简体中文' },{ code: 'en-US', name: 'English' }],languageIndex: 0,favorites: []}},onShow() {// 加载设置和收藏this.loadSettings()this.loadFavorites()},methods: {loadSettings() {const settings = stateManager.get('settings')if (settings) {// 找到主题索引this.themeIndex = this.themes.findIndex(t => t === settings.theme)