试想当我们在开发一个Vue应用程序时,如果在一个项目中频繁的使用组件传参的方式来同步data中的值,一旦项目结构变得复杂,管理和维护这些数据将变得十分繁琐,为此,Vue为这些被多个组件共同使用的data提供了一个统一的管理工具—Vuex。
Vuex是专为Vue.js应用程序开发的状态管理模式,集中存储管理应用的所有组件的状态(数据),并以相同的规则保证状态以一种可预测的方式发生变化。
安装
可在项目目录下直接通过npm
安装,其他安装方式详见Vuex安装。
使用
首先需要创建一个store
实例,引入你创建的所有modules
:
1 2 3 4 5 6
| 目录结构 /src |-main.js |-/store |-/modules |-index.js
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import Vue from 'vue' import Vuex from 'vuex' import user from './modules/user' import team from './modules/team' import product from "./modules/product"; import chat from "./modules/chat"; import notification from "./modules/notification";
Vue.use(Vuex)
export default new Vuex.Store({ modules: { user,team,product,chat,notification }, strict: true })
|
在main.js
中,引入store
实例并暴露出来:
1 2 3 4 5 6 7 8
| import Vue from 'vue' import App from './App.vue' import store from './store'
export default new Vue({ render: h => h(App), store }).$mount('#app')
|
State
State是Vuex的基本属性,称为单一状态树,如果熟悉Java面向对象编程的话,我们可以将其类比为成员变量:
1 2 3 4 5 6 7 8 9
| public class User { private String name; private Integer age; private String gender; } public class Users { private List<User> users; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| const state = () => ({ users: [ { name: ..., age: ..., gender: ..., }, ... ... ... ] })
|
在Vue组件中获得Vuex状态可通过以下两种方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| this.$store.state.name
import { mapState } from 'vuex'
export default { computed: mapState({ count: state => state.count,
countAlias: 'count',
countPlusLocalState (state) { return state.count + this.localCount } }) }
|
Getter
getter
的使用可类比为Java中的get
方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class User { private String name; private Integer age; private String gender; } public class Users { private List<User> users; public List<User> getUsers() { return users; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const state = () => ({ users: [ { name: ..., age: ..., gender: ..., }, ... ... ... ] })
const getters = { users: state => state.users, }
|
如果仅仅如此,为何不直接获取state
呢?因为有时候我们需要从state
中派生出一些状态,例如对列表进行过滤,同样我们可与Java实现类比:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public class User { private String name; private Integer age; private String gender; } public class Users { private List<User> users; public List<User> getUsers() { return users; } public List<User> getChildren() { List<User> children = new ArrayList<>; for (User item: this.users) { if (item.age <= 18) { children.add(item) } } return children; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const state = () => ({ users: [ { name: ..., age: ..., gender: ..., }, ... ... ... ] })
const getters = { users: state => state.users, children: state => state.users,filter( user => user.age <= 18), }
|
在Vue组件中我们可以通过属性访问或者通过mapGetters
来获取对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import { mapGetters } from 'vuex' export default { computed: { ...mapGetters([ 'users', ]), }, methods: { printUsers() { console.log(this.users) console.log(this.$store.getters.users) } } }
|
Mutation
提交mutation
是更改Vuex的store
中状态的唯一方法,Vuex中的mutation
类似于事件:每个mutation
都有一个字符串的事件类型(type)和一个回调函数(handler)。这个回调函数就是我们实际进行状态更改的方法,并且他会接受state
作为第一个参数。
mutation
的实际使用类似于Java中的set
方法,是设置state
值的唯一方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class User { private String name; private Integer age; private String gender; } public class Users { private List<User> users; private Boolean status; public void setStatus() { this.status = true; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const state = () => ({ users: [ { name: ..., age: ..., gender: ..., }, ... ... ... ], status: null, })
const mutations = { setStatus(state) { state.status = true } }
|
但不同于Java中set
的使用方式,我们不能直接调用一个mutation handler
,而是提交一个名为xxx
的mutation
,触发相应的mutation handler
执行具体的变更。
1 2
| Users users = new Users(); users.setStatus();
|
1
| this.$store.commit("setStatus")
|
在Java中set
函数可以传入参数进而变更成员变量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class User { private String name; private Integer age; private String gender; } public class Users { private List<User> users; private Boolean status; public void setStatus() { this.status = true; } public void setUser(User user) { this.users.add(user); } }
|
调用set
函数:
1 2
| Users users = new Users(); users.setUser(newUser);
|
在Vuex中我们也可以通过提交载荷(Payload)的方式向store.commit
传入额外的参数,即mutation
的载荷。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| const state = () => ({ users: [ { name: ..., age: ..., gender: ..., }, ... ... ... ], status: null, })
const mutations = { setStatus(state) { state.status = true }, setUser(state, user) { state.users.push(user) } }
|
在组件中提交携带载荷的mutation
:
1
| this.$store.commit('setUser', user)
|
或者使用mapMutations
映射出来:
1 2 3 4 5 6 7 8 9 10 11 12
| import { mapMutations } from 'vuex'
export default { methods: { ...mapMutations([ 'setUser', ]), appendUser(user){ this.setUser(user) }, } }
|
综上看来,mutation
的使用与set
函数的目的是相同,但mutation
在使用中最大的原则 — 必须是同步函数。
在mutation
中混合异步调用会导致你的程序很难调试,当我们调用了两个包含异步回调的mutation
来改变状态,我们无法知道什么时候回调以及哪个先回调,因此在Vuex中,mutation都是同步事务。
所以为了处理异步操作,Vuex引入了action
这一概念。
Action
action
类似于mutation
是可“调用”的方法,两者不同点在于:
action
提交mutation
,而不直接变更状态;
action
可以包含任意异步操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| const state = () => ({ users: [ { name: ..., age: ..., gender: ..., }, ... ... ... ], status: null, })
const actions = { updateUser({commit}) { request({ url: '/user/getNewUser' method: 'get' }).then(res => { commit('setUser', res.data) }) } }
const mutations = { setStatus(state) { state.status = true }, setUser(state, user) { state.users.push(user) } }
|
这段代码中我们注册了一个简单的异步action
,我们通过request
向后端发送请求,请求新用户,然后我们在回调函数中提交mutation
变更状态。
action
通过store.dispatch
触发:
1
| this.$store.dispatch('updateUser')
|
action
同样可以通过提交载荷的方式进行分发:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| const state = () => ({ users: [ { name: ..., age: ..., gender: ..., }, ... ... ... ], status: null, })
const actions = { appendUser({commit}, user) { commit('setUser', user) } }
const mutations = { setStatus(state) { state.status = true }, setUser(state, user) { state.users.push(user) } }
|
1
| this.$store.dispatch('appendUser', newUser)
|
或者使用mapAction
映射出来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import { mapActions } from 'vuex'
export default { methods: { ...mapActions([ 'appendUser', ]), test() { this.appendUser(user) } } }
|
Module
使用单一状态树,应用的所有状态都会集中到一个较大的对象,随着应用迭代变得越来越复杂,store
对象会变得越来越臃肿。为了解决以上问题,Vuex允许我们将对象模块(Module)化,每个模块拥有自己的state
、mutation
、action
、getter
甚至嵌套子模块。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| const moduleA = { state: () => ({ ... }), mutations: { ... }, actions: { ... }, getters: { ... } }
const moduleB = { state: () => ({ ... }), mutations: { ... }, actions: { ... } }
const store = new Vuex.Store({ modules: { a: moduleA, b: moduleB } })
store.state.a store.state.b
|
在默认情况下,模块内部的action
、mutation
和getter
是注册在全局命名空间的,这样使得多个模块能够对同一mutation
或action
作出响应。
如果你希望你的模块具有更高的封装度和复用性,可以通过添加``namespaces: true`的方式使其成为带命名空间的模块。
下面我们重新复习下Vuex的目录结构:
1 2 3 4 5 6 7 8 9 10 11
| 目录结构 /src |-main.js |-/store |-/modules |-user.js |-team.js |-product.js |-chat.js |-notification.js |-index.js
|
以user.js
为例,让我们看一下一个完整的Module是怎样的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| import {request} from '../../lib/network/request'
const state = () => ({ users: [ { name: ..., age: ..., gender: ..., }, ... ... ... ], status: null, })
const actions = { updateUser({commit}) { request({ url: '/user/getNewUser' method: 'get' }).then(res => { commit('setUser', res.data) }) } }
const getters = { users: state => state.users, children: state => state.users,filter( user => user.age <= 18), }
const mutations = { setStatus(state) { state.status = true }, setUser(state, user) { state.users.push(user) } }
export default { state, getters, actions, mutations }
|
然后需要在/modules
目录下的index.js
中将各个module
注册到store
对象中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import Vue from 'vue' import Vuex from 'vuex' import user from './modules/user' import team from './modules/team' import product from "./modules/product"; import chat from "./modules/chat"; import notification from "./modules/notification";
Vue.use(Vuex)
export default new Vuex.Store({ modules: { user,team,product,chat,notification }, strict: true })
|
然后在main.js
中将store
放进我们的Vue应用程序中:
1 2 3 4 5 6 7 8 9
| import Vue from 'vue' import App from './App.vue' import store from './store'
export default new Vue({ render: h => h(App), store }).$mount('#app')
|
至此,这便是一个完整的Vuex的使用实例,虽然在Vue中我们也可以通过属性传递的方式在不同组件之间传递data,但是当同一个data需要被多个组件同时调用,数据的一致性便很难保证,Vuex的引入则很好的解决了这一问题,Vuex中状态的变化是全局的,是实时计算的,当我们getter
的计算依托于多个state
时,当我们提交了新的commit
变更状态,相应的getter
返回值也会变化,这让我们不用过多分心于数据的一致性。
Vuex可以帮助我们管理共享状态,在应用并不复杂的情况下使用Vuex可能会有些多余,但是当构建一个复杂应用程序时,Vuex对于管理共享状态会是一个很好的选择。