vuex 作为 vue 生态中用于状态管理的一种模式,已被广泛应用于 vue 单页应用开发中。下面谈谈自己对 vuex 的一些个人见解以及在实际项目中的应用场景。
vuex 的几个核心概念Store:Vuex 使用一个 Store 对象管理应用的状态,一个 Store 包括 State, Getter, Mutation, Action 四个属性。
State:State 意为“状态”,是 vuex 状态管理的数据源,我个人觉得相当于数据库。
Getter:Getter 的作用与 filters 有一些相似,可以将 State 进行过滤后输出。(参考:https://www.jb51.net/article/159727.htm)
1 应用场景
假设我们在 Vuex 中定义了一个数组
1
2
3
4
5
6
|
const store =
new
Vuex.Store({
state: {
list:[1,3,5,7,9,20,30]
}
...
})
|
业务场景希望过滤出大于 5 的数。马上想到的方法可能的是:在组件的计算属性中进行过滤:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
<template>
<div>
{{list}}
</div>
</template>
<script>
export
default
{
name:
"index.vue"
,
computed: {
list() {
return
this
.$store.state.list.filter(item => item > 5);
}
}
}
</script>
|
效果:
功能虽然实现了,但如果其它组件也需要过滤后的数据,那么就得把 index.vue 中的计算过滤代码复制出来。如果过滤规则发生变化,还得一一修改这些组件中的计算属性,很难维护。这种场景下,我们就可以使用 getters 属性啦O(∩_∩)O~
2 基础用法
main.js:
1
2
3
4
5
6
7
8
9
10
|
const store =
new
Vuex.Store({
state: {
list: [1, 3, 5, 7, 9, 20, 30]
},
getters: {
filteredList: state => {
return
state.list.filter(item => item > 5)
}
}
})
|
index.vue:
1
2
3
4
5
6
7
8
9
10
|
<script>
export
default
{
name:
"index.vue"
,
computed: {
list() {
return
this
.$store.getters.filteredList;
}
}
}
</script>
|
效果达到了,而且只需要在一处维护过滤规则即可。
3 内部依赖
getter 可以依赖其它已经定义好的 getter。比如我们需要统计过滤后的列表数量,就可以依赖之前定义好的过滤函数。
main.js:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
const store =
new
Vuex.Store({
state: {
list: [1, 3, 5, 7, 9, 20, 30]
},
getters: {
filteredList: state => {
return
state.list.filter(item => item > 5)
},
listCount: (state, getters) => {
return
getters.filteredList.length;
}
}
})
|
index.vue:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
<template>
<div>
过滤后的列表:{{list}}
<br>
列表长度:{{listCount}}
</div>
</template>
<script>
export
default
{
name:
"index.vue"
,
computed: {
list() {
return
this
.$store.getters.filteredList;
},
listCount() {
return
this
.$store.getters.listCount;
}
}
}
</script>
|
效果:
Mutation:Mutaion 是 vuex 中改变 State 的唯一途径(严格模式下),并且只能是同步操作。
Action:一些对 State 的异步操作和业务逻辑操作可以放在 Action 中,并通过在 Action 提交 Mutaion 变更状态。(见下面用户登录和用户信息demo)
Module:当 Store 对象过于庞大时,可根据具体的业务需求分为多个 Module。
关于用户登录和用户信息模块的module
1 import storage from 'store' 2 import { login, getInfo, logout } from '@/api/login' 3 import { ACCESS_TOKEN } from '@/store/mutation-types' 4 import { welcome } from '@/utils/util' 5 import Vue from 'vue' 6 7 const user = { 8 state: { 9 token: '', 10 name: '', 11 welcome: '', 12 avatar: '', 13 roles: [], 14 info: {} 15 }, 16 17 mutations: { 18 SET_TOKEN: (state, token) => { 19 state.token = token 20 }, 21 SET_NAME: (state, { name, welcome }) => { 22 state.name = name 23 state.welcome = welcome 24 }, 25 SET_AVATAR: (state, avatar) => { 26 state.avatar = avatar 27 }, 28 SET_ROLES: (state, roles) => { 29 state.roles = roles 30 }, 31 SET_INFO: (state, info) => { 32 state.info = info 33 } 34 }, 35 36 actions: { 37 // 登录 38 Login({ commit }, userInfo) { 39 return new Promise((resolve, reject) => { 40 login(userInfo).then(response => { 41 const result = response.result 42 storage.set(ACCESS_TOKEN, result.token, 7 * 24 * 60 * 60 * 1000) 43 commit('SET_TOKEN', result.token) 44 resolve() 45 }).catch(error => { 46 reject(error) 47 }) 48 }) 49 }, 50 51 // 获取用户信息 52 GetInfo({ commit }) { 53 return new Promise((resolve, reject) => { 54 getInfo().then(response => { 55 const result = response.result 56 57 if (result.role && result.role.permissions.length > 0) { 58 const role = result.role 59 role.permissions = result.role.permissions 60 role.permissions.map(per => { 61 if (per.actionEntitySet != null && per.actionEntitySet.length > 0) { 62 const action = per.actionEntitySet.map(action => { return action.action }) 63 per.actionList = action 64 } 65 }) 66 role.permissionList = role.permissions.map(permission => { return permission.permissionId }) 67 commit('SET_ROLES', result.role) 68 commit('SET_INFO', result) 69 } else { 70 reject(new Error('getInfo: roles must be a non-null array !')) 71 } 72 73 commit('SET_NAME', { name: result.name, welcome: welcome() }) 74 commit('SET_AVATAR', result.avatar) 75 76 resolve(response) 77 }).catch(error => { 78 reject(error) 79 }) 80 }) 81 }, 82 83 // 登出 84 Logout({ commit, state }) { 85 return new Promise((resolve) => { 86 logout(state.token).then(() => { 87 resolve() 88 }).catch(() => { 89 resolve() 90 }).finally(() => { 91 commit('SET_TOKEN', '') 92 commit('SET_ROLES', []) 93 storage.remove(ACCESS_TOKEN) 94 }) 95 }) 96 } 97 98 } 99 } 100 101 export default user
每个module下state里的数据,绑定到根store上
const getters = { isMobile: state => state.app.isMobile, lang: state => state.app.lang, theme: state => state.app.theme, qualityCheckInfo: state => state.app.qualitycCheckBaseInfo, color: state => state.app.color, token: state => state.user.token, avatar: state => state.user.avatar, nickname: state => state.user.name, welcome: state => state.user.welcome, roles: state => state.user.roles, userInfo: state => state.user.info, addRouters: state => state.permission.addRouters, multiTab: state => state.app.multiTab } export default getters
导出store的index.js文件
1 import Vue from 'vue' 2 import Vuex from 'vuex' 3 4 import app from './modules/app' 5 import user from './modules/user' 6 7 // default router permission control 8 import permission from './modules/permission' 9 10 // dynamic router permission control (Experimental) 11 // import permission from './modules/async-router' 12 import getters from './getters' 13 14 Vue.use(Vuex) 15 16 export default new Vuex.Store({ 17 modules: { 18 app, 19 user, 20 permission 21 }, 22 state: { 23 24 }, 25 mutations: { 26 27 }, 28 actions: { 29 30 }, 31 getters 32 })
上图为官网中 vuex 各个要素的关系图,总的来说,我们可以在组件中触发 Action,Action 则会提交 Mutation,Mutaion 会对 State 进行修改,组件再根据 State 、Getter 渲染页面。
什么样的应用场景下需要 vuex ?如果不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果你的应用够简单,那最好不要使用 Vuex。一个简单的 global event bus 就足够所需了。但是,如果需要构建是一个中大型单页应用,很可能会考虑如何更好地在组件外部管理状态,Vuex 将会成为自然而然的选择。
vuex 一般用于中大型 web 单页应用中对应用的状态进行管理,对于一些组件间关系较为简单的小型应用,使用 vuex 的必要性不是很大,因为完全可以用组件 prop 属性或者事件来完成父子组件之间的通信,vuex 更多地用于解决跨组件通信以及作为数据中心集中式存储数据。
使用 vuex 解决跨组件通信问题
跨组件通信一般指非父子组件间的通信,父子组件的通信一般可以通过以下方式:
1、通过 prop 属性实现父组件向子组件传递数据
2、通过在子组件中触发事件实现向父组件传递数据
非父子组件之间的通信一般通过一个空的 Vue 实例作为 中转站,也可以称之为 事件中心、event bus。
// 创建事件中心实例
let bus = new Vue()
// 在组件 A 中触发事件
bus.$emit('test', 1)
// 在组件 B 中接受事件
bus.$on('test', (id) => {
// ...
})
采用 event bus 的方式适合简单的跨组件事件触发,对于多层级组件嵌套等较为复杂的场景,使用 vuex 能更好地应对。vuex 是通过将 state 作为数据中心、各个组件共享 state 实现跨组件通信的,此时的数据完全独立于组件,因此将组件间共享的数据置于 State 中能有效解决多层级组件嵌套的跨组件通信问题。
vuex 作为数据存储中心
vuex 的 State 在单页应用的开发中本身具有一个“数据库”的作用,可以将组件中用到的数据存储在 State 中,并在 Action 中封装数据读写的逻辑。这时候存在一个问题,一般什么样的数据会放在 State 中呢? 目前主要有两种数据会使用 vuex 进行管理:
1、组件之间全局共享的数据
2、通过后端异步请求的数据
本人所在团队在实际的项目开发过程中更多的是应用第二种,即把通过后端异步请求的数据都纳入 vuex 状态管理,在 Action 中封装数据的增删改查等逻辑,这样可以一定程度上对前端的逻辑代码进行分层,使组件中的代码更多地关注页面交互与数据渲染等视图层的逻辑,而异步请求与状态数据的持久化等则交由 vuex 管理。
总结:vuex 具体应用在哪取决于项目的规模以及具体的业务场景,可能是为了解决多层嵌套组件之间的通信问题,或是为了更好地管理应用中错综复杂的状态关系,而不能为了用 vuex 而在项目中使用 vuex。
参考: https://zhuanlan.zhihu.com/p/41237859
见下面用户登录和用户信息demo