uni-app 状态管理
章节介绍
在复杂的应用中,状态管理是一个重要的挑战。当应用规模扩大,组件层级变深时,组件间的通信会变得复杂和难以维护。uni-app 集成了 Vuex 作为官方推荐的状态管理方案,帮助开发者集中管理应用状态,实现组件间的高效通信。本章节将详细介绍 uni-app 中的状态管理方法,包括 Vuex 的基本使用、状态管理的最佳实践以及模块化状态管理的实现,帮助你构建可维护性更高的应用。
核心知识点讲解
1. Vuex 概述
Vuex 是一个专为 Vue.js 应用设计的状态管理模式,它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
Vuex 的核心概念包括:
- State:存储应用状态的对象
- Getter:从 State 中派生出的计算属性
- Mutation:修改 State 的唯一方法,必须是同步函数
- Action:处理异步操作,可以提交 Mutation
- Module:将 Store 分割成模块化的结构
2. Vuex 基本使用
安装和配置 Vuex
在 uni-app 项目中,Vuex 已经内置,无需单独安装。你只需要创建 Store 实例并配置即可。
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
// 状态
},
mutations: {
// 修改状态的方法
},
actions: {
// 异步操作
},
getters: {
// 计算属性
},
modules: {
// 模块化
}
})在 main.js 中引入 Store
// main.js
import Vue from 'vue'
import App from './App.vue'
import store from './store'
Vue.config.productionTip = false
App.mpType = 'app'
const app = new Vue({
...App,
store
})
app.$mount()3. State 和 Getter
State
State 是存储应用状态的对象,类似于组件中的 data 属性。
// store/index.js
state: {
count: 0,
user: null,
isLoggedIn: false
}在组件中访问 State:
// 方法一:直接访问
this.$store.state.count
// 方法二:使用计算属性
computed: {
count() {
return this.$store.state.count
}
}
// 方法三:使用 mapState 辅助函数
import { mapState } from 'vuex'
computed: {
...mapState(['count', 'user', 'isLoggedIn'])
}Getter
Getter 是从 State 中派生出的计算属性,类似于组件中的 computed 属性。
// store/index.js
getters: {
doubleCount: state => state.count * 2,
userInfo: state => state.user,
isAuthenticated: state => state.isLoggedIn
}在组件中访问 Getter:
// 方法一:直接访问
this.$store.getters.doubleCount
// 方法二:使用计算属性
computed: {
doubleCount() {
return this.$store.getters.doubleCount
}
}
// 方法三:使用 mapGetters 辅助函数
import { mapGetters } from 'vuex'
computed: {
...mapGetters(['doubleCount', 'userInfo', 'isAuthenticated'])
}4. Mutation 和 Action
Mutation
Mutation 是修改 State 的唯一方法,必须是同步函数。
// store/index.js
mutations: {
increment(state) {
state.count++
},
setUser(state, user) {
state.user = user
state.isLoggedIn = true
},
logout(state) {
state.user = null
state.isLoggedIn = false
}
}在组件中提交 Mutation:
// 方法一:直接提交
this.$store.commit('increment')
this.$store.commit('setUser', user)
// 方法二:使用 mapMutations 辅助函数
import { mapMutations } from 'vuex'
methods: {
...mapMutations(['increment', 'setUser', 'logout'])
}Action
Action 用于处理异步操作,可以提交 Mutation。
// store/index.js
actions: {
async login({ commit }, userInfo) {
try {
// 模拟 API 请求
const response = await uni.request({
url: 'https://api.example.com/login',
method: 'POST',
data: userInfo
})
const user = response.data
commit('setUser', user)
return user
} catch (error) {
console.error('Login failed:', error)
throw error
}
},
async logout({ commit }) {
try {
// 模拟 API 请求
await uni.request({
url: 'https://api.example.com/logout',
method: 'POST'
})
commit('logout')
} catch (error) {
console.error('Logout failed:', error)
throw error
}
}
}在组件中分发 Action:
// 方法一:直接分发
this.$store.dispatch('login', userInfo)
.then(user => {
console.log('Login successful:', user)
})
.catch(error => {
console.error('Login failed:', error)
})
// 方法二:使用 mapActions 辅助函数
import { mapActions } from 'vuex'
methods: {
...mapActions(['login', 'logout'])
}
// 使用
this.login(userInfo)
.then(user => {
console.log('Login successful:', user)
})
.catch(error => {
console.error('Login failed:', error)
})5. 模块化状态管理
当应用规模扩大时,单一的 Store 会变得臃肿和难以维护。Vuex 提供了模块化的功能,允许你将 Store 分割成多个模块。
创建模块
// store/modules/user.js
const userModule = {
namespaced: true,
state: {
user: null,
isLoggedIn: false,
token: null
},
mutations: {
setUser(state, user) {
state.user = user
state.isLoggedIn = true
},
setToken(state, token) {
state.token = token
},
logout(state) {
state.user = null
state.isLoggedIn = false
state.token = null
}
},
actions: {
async login({ commit }, userInfo) {
try {
const response = await uni.request({
url: 'https://api.example.com/login',
method: 'POST',
data: userInfo
})
const { user, token } = response.data
commit('setUser', user)
commit('setToken', token)
return user
} catch (error) {
console.error('Login failed:', error)
throw error
}
},
async logout({ commit }) {
try {
await uni.request({
url: 'https://api.example.com/logout',
method: 'POST'
})
commit('logout')
} catch (error) {
console.error('Logout failed:', error)
throw error
}
}
},
getters: {
userInfo: state => state.user,
isAuthenticated: state => state.isLoggedIn,
authToken: state => state.token
}
}
export default userModule// store/modules/counter.js
const counterModule = {
namespaced: true,
state: {
count: 0
},
mutations: {
increment(state) {
state.count++
},
decrement(state) {
state.count--
},
setCount(state, count) {
state.count = count
}
},
actions: {
incrementAsync({ commit }) {
return new Promise(resolve => {
setTimeout(() => {
commit('increment')
resolve()
}, 1000)
})
}
},
getters: {
doubleCount: state => state.count * 2,
tripleCount: state => state.count * 3
}
}
export default counterModule注册模块
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import user from './modules/user'
import counter from './modules/counter'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
user,
counter
}
})使用模块化状态
// 访问模块状态
this.$store.state.user.user
this.$store.state.counter.count
// 访问模块 getter
this.$store.getters['user/userInfo']
this.$store.getters['counter/doubleCount']
// 提交模块 mutation
this.$store.commit('user/setUser', user)
this.$store.commit('counter/increment')
// 分发模块 action
this.$store.dispatch('user/login', userInfo)
this.$store.dispatch('counter/incrementAsync')
// 使用辅助函数
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'
computed: {
...mapState('user', ['user', 'isLoggedIn']),
...mapState('counter', ['count']),
...mapGetters('user', ['userInfo', 'isAuthenticated']),
...mapGetters('counter', ['doubleCount'])
},
methods: {
...mapMutations('user', ['setUser', 'logout']),
...mapMutations('counter', ['increment', 'decrement']),
...mapActions('user', ['login', 'logout']),
...mapActions('counter', ['incrementAsync'])
}实用案例分析
案例:实现全局用户状态管理
1. 创建用户状态模块
// store/modules/user.js
const userModule = {
namespaced: true,
state: {
user: null,
isLoggedIn: false,
token: null,
loading: false,
error: null
},
mutations: {
setLoading(state, loading) {
state.loading = loading
},
setError(state, error) {
state.error = error
},
setUser(state, user) {
state.user = user
state.isLoggedIn = true
state.error = null
},
setToken(state, token) {
state.token = token
},
logout(state) {
state.user = null
state.isLoggedIn = false
state.token = null
state.error = null
}
},
actions: {
async login({ commit }, userInfo) {
try {
commit('setLoading', true)
commit('setError', null)
// 模拟 API 请求
const response = await uni.request({
url: 'https://api.example.com/login',
method: 'POST',
data: userInfo
})
if (response.statusCode === 200) {
const { user, token } = response.data
commit('setUser', user)
commit('setToken', token)
// 存储 token 到本地存储
uni.setStorageSync('token', token)
return user
} else {
throw new Error('Login failed')
}
} catch (error) {
commit('setError', error.message)
throw error
} finally {
commit('setLoading', false)
}
},
async logout({ commit }) {
try {
commit('setLoading', true)
// 模拟 API 请求
await uni.request({
url: 'https://api.example.com/logout',
method: 'POST'
})
// 清除本地存储的 token
uni.removeStorageSync('token')
commit('logout')
} catch (error) {
console.error('Logout failed:', error)
// 即使 API 请求失败,也清除本地状态
uni.removeStorageSync('token')
commit('logout')
} finally {
commit('setLoading', false)
}
},
async checkAuth({ commit }) {
try {
// 从本地存储获取 token
const token = uni.getStorageSync('token')
if (!token) {
commit('logout')
return false
}
commit('setLoading', true)
// 验证 token
const response = await uni.request({
url: 'https://api.example.com/verify-token',
method: 'POST',
data: { token }
})
if (response.statusCode === 200) {
const user = response.data.user
commit('setUser', user)
commit('setToken', token)
return true
} else {
uni.removeStorageSync('token')
commit('logout')
return false
}
} catch (error) {
console.error('Check auth failed:', error)
uni.removeStorageSync('token')
commit('logout')
return false
} finally {
commit('setLoading', false)
}
}
},
getters: {
userInfo: state => state.user,
isAuthenticated: state => state.isLoggedIn,
authToken: state => state.token,
isLoading: state => state.loading,
error: state => state.error
}
}
export default userModule2. 在应用启动时检查认证状态
// main.js
import Vue from 'vue'
import App from './App.vue'
import store from './store'
Vue.config.productionTip = false
App.mpType = 'app'
const app = new Vue({
...App,
store
})
// 应用启动时检查认证状态
store.dispatch('user/checkAuth')
.then(isAuthenticated => {
console.log('Auth check result:', isAuthenticated)
})
.catch(error => {
console.error('Auth check failed:', error)
})
app.$mount()3. 登录页面示例
<!-- pages/login/login.vue -->
<template>
<view class="container">
<view class="login-form">
<text class="title">登录</text>
<view class="form-item">
<input
type="text"
v-model="userInfo.username"
placeholder="请输入用户名"
class="input"
/>
</view>
<view class="form-item">
<input
type="password"
v-model="userInfo.password"
placeholder="请输入密码"
class="input"
/>
</view>
<text v-if="error" class="error-message">{{ error }}</text>
<button
@click="handleLogin"
:disabled="loading"
class="login-button"
>
{{ loading ? '登录中...' : '登录' }}
</button>
</view>
</view>
</template>
<script>
import { mapState, mapActions } from 'vuex'
export default {
data() {
return {
userInfo: {
username: '',
password: ''
}
}
},
computed: {
...mapState('user', ['loading', 'error', 'isAuthenticated'])
},
watch: {
// 监听认证状态变化,登录成功后跳转到首页
isAuthenticated(newVal) {
if (newVal) {
uni.switchTab({
url: '/pages/index/index'
})
}
}
},
methods: {
...mapActions('user', ['login']),
handleLogin() {
if (!this.userInfo.username || !this.userInfo.password) {
uni.showToast({
title: '请输入用户名和密码',
icon: 'none'
})
return
}
this.login(this.userInfo)
.catch(error => {
console.error('Login failed:', error)
})
}
}
}
</script>
<style scoped>
.container {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
padding: 40rpx;
background-color: #f5f5f5;
}
.login-form {
width: 100%;
max-width: 500rpx;
background-color: #fff;
padding: 60rpx;
border-radius: 20rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
}
.title {
font-size: 36rpx;
font-weight: bold;
text-align: center;
margin-bottom: 60rpx;
color: #333;
}
.form-item {
margin-bottom: 40rpx;
}
.input {
width: 100%;
height: 80rpx;
border: 2rpx solid #e0e0e0;
border-radius: 10rpx;
padding: 0 20rpx;
font-size: 28rpx;
}
.error-message {
color: #ff4d4f;
font-size: 24rpx;
margin-bottom: 30rpx;
display: block;
}
.login-button {
width: 100%;
height: 80rpx;
background-color: #409eff;
color: #fff;
font-size: 28rpx;
font-weight: bold;
border-radius: 10rpx;
margin-top: 20rpx;
}
.login-button:disabled {
background-color: #c0c4cc;
}
</style>4. 首页示例
<!-- pages/index/index.vue -->
<template>
<view class="container">
<view v-if="isAuthenticated" class="user-info">
<text class="welcome">欢迎,{{ userInfo.username }}</text>
<button @click="handleLogout" class="logout-button">退出登录</button>
</view>
<view v-else class="login-prompt">
<text>请先登录</text>
<button @click="navigateToLogin" class="login-button">去登录</button>
</view>
</view>
</template>
<script>
import { mapState, mapActions } from 'vuex'
export default {
computed: {
...mapState('user', ['isAuthenticated', 'userInfo'])
},
methods: {
...mapActions('user', ['logout']),
handleLogout() {
uni.showModal({
title: '退出登录',
content: '确定要退出登录吗?',
success: (res) => {
if (res.confirm) {
this.logout()
.then(() => {
uni.switchTab({
url: '/pages/index/index'
})
})
.catch(error => {
console.error('Logout failed:', error)
})
}
}
})
},
navigateToLogin() {
uni.navigateTo({
url: '/pages/login/login'
})
}
}
}
</script>
<style scoped>
.container {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 40rpx;
background-color: #f5f5f5;
}
.user-info {
text-align: center;
}
.welcome {
font-size: 32rpx;
color: #333;
margin-bottom: 40rpx;
display: block;
}
.logout-button {
width: 200rpx;
height: 60rpx;
background-color: #ff4d4f;
color: #fff;
font-size: 24rpx;
border-radius: 10rpx;
}
.login-prompt {
text-align: center;
}
.login-prompt text {
font-size: 32rpx;
color: #666;
margin-bottom: 40rpx;
display: block;
}
.login-button {
width: 200rpx;
height: 60rpx;
background-color: #409eff;
color: #fff;
font-size: 24rpx;
border-radius: 10rpx;
}
</style>常见问题与解决方案
1. 状态持久化
问题:刷新页面后,Vuex 状态会丢失。
解决方案:
- 使用
uni.setStorageSync和uni.getStorageSync存储和读取状态 - 在 Mutation 中同步更新本地存储
- 在应用启动时从本地存储恢复状态
// store/modules/user.js
mutations: {
setToken(state, token) {
state.token = token
// 同步更新本地存储
uni.setStorageSync('token', token)
},
logout(state) {
state.user = null
state.isLoggedIn = false
state.token = null
// 清除本地存储
uni.removeStorageSync('token')
}
}
// 应用启动时恢复状态
const token = uni.getStorageSync('token')
if (token) {
// 验证 token 有效性
store.dispatch('user/checkAuth')
}2. 模块化状态管理中的命名冲突
问题:多个模块中存在相同名称的 Mutation 或 Action 时,会发生命名冲突。
解决方案:
- 在模块中设置
namespaced: true,启用命名空间 - 使用模块名称作为前缀访问模块的状态、Getter、Mutation 和 Action
// 启用命名空间
const userModule = {
namespaced: true,
// ...
}
// 使用命名空间访问
this.$store.commit('user/setUser', user)
this.$store.dispatch('user/login', userInfo)3. 异步操作中的状态管理
问题:在异步操作中,状态更新可能不及时或不一致。
解决方案:
- 使用 Action 处理异步操作,在异步操作完成后提交 Mutation
- 使用 Promise 或 async/await 处理异步流程
- 添加 loading 状态,提升用户体验
// store/modules/user.js
actions: {
async login({ commit }, userInfo) {
try {
commit('setLoading', true)
// 异步操作
const response = await uni.request({/* ... */})
// 提交 Mutation 更新状态
commit('setUser', response.data.user)
commit('setToken', response.data.token)
return response.data.user
} catch (error) {
commit('setError', error.message)
throw error
} finally {
commit('setLoading', false)
}
}
}代码优化建议
1. 使用辅助函数简化代码
// 优化前:直接访问和提交
this.$store.state.user.user
this.$store.commit('user/setUser', user)
this.$store.dispatch('user/login', userInfo)
// 优化后:使用辅助函数
import { mapState, mapMutations, mapActions } from 'vuex'
computed: {
...mapState('user', ['user', 'isLoggedIn'])
},
methods: {
...mapMutations('user', ['setUser']),
...mapActions('user', ['login'])
}
// 使用
this.user
this.setUser(user)
this.login(userInfo)2. 拆分大型 Store
// 优化前:单一大型 Store
const store = new Vuex.Store({
state: {
// 大量状态
},
mutations: {
// 大量 mutations
},
actions: {
// 大量 actions
}
})
// 优化后:模块化 Store
const store = new Vuex.Store({
modules: {
user: require('./modules/user').default,
counter: require('./modules/counter').default,
products: require('./modules/products').default
}
})3. 使用常量定义 Mutation 类型
// 优化前:直接使用字符串
commit('setUser', user)
// 优化后:使用常量
// store/types.js
export const SET_USER = 'SET_USER'
export const LOGOUT = 'LOGOUT'
// store/modules/user.js
import { SET_USER, LOGOUT } from '../types'
mutations: {
[SET_USER](state, user) {
state.user = user
state.isLoggedIn = true
},
[LOGOUT](state) {
state.user = null
state.isLoggedIn = false
}
}
// 使用
commit(SET_USER, user)4. 合理使用 Getter
// 优化前:在组件中计算
computed: {
fullName() {
const user = this.$store.state.user.user
return user ? `${user.firstName} ${user.lastName}` : ''
}
}
// 优化后:在 Store 中使用 Getter
// store/modules/user.js
getters: {
fullName: state => {
return state.user ? `${state.user.firstName} ${state.user.lastName}` : ''
}
}
// 组件中使用
computed: {
...mapGetters('user', ['fullName'])
}章节总结
本章节详细介绍了 uni-app 中的状态管理方法,包括:
- Vuex 概述:介绍了 Vuex 的核心概念,包括 State、Getter、Mutation、Action 和 Module
- Vuex 基本使用:讲解了如何安装和配置 Vuex,以及如何在组件中使用 Vuex
- State 和 Getter:详细介绍了如何定义和访问 State,以及如何使用 Getter 派生计算属性
- Mutation 和 Action:讲解了如何使用 Mutation 修改状态,以及如何使用 Action 处理异步操作
- 模块化状态管理:介绍了如何将 Store 分割成多个模块,以及如何使用模块化状态
- 实用案例:通过实现全局用户状态管理,展示了状态管理在实际应用中的使用
- 常见问题与解决方案:针对状态持久化、命名冲突和异步操作中的状态管理问题提供了解决方案
- 代码优化建议:提供了使用辅助函数、拆分大型 Store、使用常量定义 Mutation 类型和合理使用 Getter 的优化建议
通过本章节的学习,你应该能够掌握 uni-app 中的状态管理方法,有效地管理应用状态,构建可维护性更高的应用。在实际开发中,应根据应用的规模和复杂度选择合适的状态管理方案,并遵循 Vuex 的最佳实践,确保状态管理的清晰和高效。