试想当我们在开发一个Vue应用程序时,如果在一个项目中频繁的使用组件传参的方式来同步data中的值,一旦项目结构变得复杂,管理和维护这些数据将变得十分繁琐,为此,Vue为这些被多个组件共同使用的data提供了一个统一的管理工具---Vuex。
Vuex是专为Vue.js应用程序开发的状态管理模式,集中存储管理应用的所有组件的状态(数据),并以相同的规则保证状态以一种可预测的方式发生变化。
可在项目目录下直接通过npm
安装,其他安装方式详见Vuex安装。
npm install vuex --save
首先需要创建一个store
实例,引入你创建的所有modules
:
目录结构/src|-main.js|-/store|-/modules|-index.js
// index.js
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
实例并暴露出来:
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是Vuex的基本属性,称为单一状态树,如果熟悉Java面向对象编程的话,我们可以将其类比为成员变量:
public class User {//成员变量private String name;private Integer age;private String gender;}public class Users {private List
const state = () => ({//Stateusers: [{name: ...,age: ...,gender: ..., },.........]
})
在Vue组件中获得Vuex状态可通过以下两种方式:
//方法一:在根实例中注册store选项,该实例会注入到根组件下的所有子组件中this.$store.state.name//方法二:使用mapState辅助函数import { mapState } from 'vuex'export default {// ...computed: mapState({// 箭头函数可使代码更简练count: state => state.count,// 传字符串参数 'count' 等同于 `state => state.count`countAlias: 'count',// 为了能够使用 `this` 获取局部状态,必须使用常规函数countPlusLocalState (state) {return state.count + this.localCount}})}
getter
的使用可类比为Java中的get
方法。
public class User {//成员变量private String name;private Integer age;private String gender;}public class Users {private List
//State
const state = () => ({users: [{name: ...,age: ...,gender: ..., },.........]
})//getter
const getters = {users: state => state.users,
}
如果仅仅如此,为何不直接获取state
呢?因为有时候我们需要从state
中派生出一些状态,例如对列表进行过滤,同样我们可与Java实现类比:
public class User {//成员变量private String name;private Integer age;private String gender;}public class Users {private List
//State
const state = () => ({users: [{name: ...,age: ...,gender: ..., },.........]
})//getter
const getters &#61; {users: state &#61;> state.users,//返回18岁及以下用户对象children: state &#61;> state.users,filter(user &#61;> user.age <&#61; 18),
}
在Vue组件中我们可以通过属性访问或者通过mapGetters
来获取对象&#xff1a;
import { mapGetters } from &#39;vuex&#39;export default {computed: {...mapGetters([&#39;users&#39;,]),},methods: {printUsers() {//通过mapGetters访问console.log(this.users)//通过属性访问console.log(this.$store.getters.users)}}}
提交mutation
是更改Vuex的store
中状态的唯一方法&#xff0c;Vuex中的mutation
类似于事件&#xff1a;每个mutation
都有一个字符串的事件类型&#xff08;type&#xff09;和一个回调函数&#xff08;handler&#xff09;。这个回调函数就是我们实际进行状态更改的方法&#xff0c;并且他会接受state
作为第一个参数。
mutation
的实际使用类似于Java中的set
方法&#xff0c;是设置state
值的唯一方式。
public class User {//成员变量private String name;private Integer age;private String gender;}public class Users {private List
//State
const state &#61; () &#61;> ({users: [{name: ...,age: ...,gender: ..., },.........],status: null,
})//mutation
const mutations &#61; {setStatus(state) {state.status &#61; true}
}
但不同于Java中set
的使用方式&#xff0c;我们不能直接调用一个mutation handler
&#xff0c;而是提交一个名为xxx
的mutation
&#xff0c;触发相应的mutation handler
执行具体的变更。
Users users &#61; new Users();
users.setStatus();
this.$store.commit("setStatus")
在Java中set
函数可以传入参数进而变更成员变量。
public class User {//成员变量private String name;private Integer age;private String gender;}public class Users {private List
调用set
函数&#xff1a;
Users users &#61; new Users();users.setUser(newUser);
在Vuex中我们也可以通过提交载荷&#xff08;Payload&#xff09;的方式向store.commit
传入额外的参数&#xff0c;即mutation
的载荷。
//Stateconst state &#61; () &#61;> ({users: [{name: ...,age: ...,gender: ..., },.........],status: null,})//mutationconst mutations &#61; {setStatus(state) {state.status &#61; true},setUser(state, user) {state.users.push(user)}}
在组件中提交携带载荷的mutation
&#xff1a;
this.$store.commit(&#39;setUser&#39;, user)
或者使用mapMutations
映射出来&#xff1a;
import { mapMutations } from &#39;vuex&#39;export default {methods: {...mapMutations([&#39;setUser&#39;,]),appendUser(user){this.setUser(user)},}}
综上看来&#xff0c;mutation
的使用与set
函数的目的是相同&#xff0c;但mutation
在使用中最大的原则 --- 必须是同步函数。
在mutation
中混合异步调用会导致你的程序很难调试&#xff0c;当我们调用了两个包含异步回调的mutation
来改变状态&#xff0c;我们无法知道什么时候回调以及哪个先回调&#xff0c;因此在Vuex中&#xff0c;mutation都是同步事务。
action
类似于mutation
是可“调用”的方法&#xff0c;两者不同点在于&#xff1a;
action
提交mutation
&#xff0c;而不直接变更状态&#xff1b;action
可以包含任意异步操作。 //Stateconst state &#61; () &#61;> ({users: [{name: ...,age: ...,gender: ..., },.........],status: null,})//actionconst actions &#61; {updateUser({commit}) {request({url: &#39;/user/getNewUser&#39;method: &#39;get&#39;}).then(res &#61;> {commit(&#39;setUser&#39;, res.data)})}}//mutationconst mutations &#61; {setStatus(state) {state.status &#61; true},setUser(state, user) {state.users.push(user)}}
这段代码中我们注册了一个简单的异步action
&#xff0c;我们通过request
向后端发送请求&#xff0c;请求新用户&#xff0c;然后我们在回调函数中提交mutation
变更状态。
this.$store.dispatch(&#39;updateUser&#39;)
action
同样可以通过提交载荷的方式进行分发&#xff1a;
//Stateconst state &#61; () &#61;> ({users: [{name: ...,age: ...,gender: ..., },.........],status: null,})//actionconst actions &#61; {appendUser({commit}, user) {commit(&#39;setUser&#39;, user)}}//mutationconst mutations &#61; {setStatus(state) {state.status &#61; true},setUser(state, user) {state.users.push(user)}}
this.$store.dispatch(&#39;appendUser&#39;, newUser)
或者使用mapAction
映射出来&#xff1a;
import { mapActions } from &#39;vuex&#39;export default {// ...methods: {...mapActions([&#39;appendUser&#39;,]),test() {this.appendUser(user)}}}
使用单一状态树&#xff0c;应用的所有状态都会集中到一个较大的对象&#xff0c;随着应用迭代变得越来越复杂&#xff0c;store
对象会变得越来越臃肿。为了解决以上问题&#xff0c;Vuex允许我们将对象模块&#xff08;Module&#xff09;化&#xff0c;每个模块拥有自己的state
、mutation
、action
、getter
甚至嵌套子模块。
const moduleA &#61; {state: () &#61;> ({ ... }),mutations: { ... },actions: { ... },getters: { ... }}const moduleB &#61; {state: () &#61;> ({ ... }),mutations: { ... },actions: { ... }}const store &#61; new Vuex.Store({modules: {a: moduleA,b: moduleB}})store.state.a // -> moduleA 的状态store.state.b // -> moduleB 的状态
在默认情况下&#xff0c;模块内部的action
、mutation
和getter
是注册在全局命名空间的&#xff0c;这样使得多个模块能够对同一mutation
或action
作出响应。
目录结构/src&#xff5c;-main.js&#xff5c;-/store&#xff5c;-/modules|-user.js|-team.js|-product.js|-chat.js|-notification.js&#xff5c;-index.js
以user.js
为例&#xff0c;让我们看一下一个完整的Module是怎样的。
//user.jsimport {request} from &#39;../../lib/network/request&#39;//Stateconst state &#61; () &#61;> ({users: [{name: ...,age: ...,gender: ..., },.........],status: null,})//actionconst actions &#61; {updateUser({commit}) {request({url: &#39;/user/getNewUser&#39;method: &#39;get&#39;}).then(res &#61;> {commit(&#39;setUser&#39;, res.data)})}}//getterconst getters &#61; {users: state &#61;> state.users,//返回18岁及以下用户对象children: state &#61;> state.users,filter(user &#61;> user.age <&#61; 18),}//mutationconst mutations &#61; {setStatus(state) {state.status &#61; true},setUser(state, user) {state.users.push(user)}}export default {state,getters,actions,mutations}
然后需要在/modules
目录下的index.js
中将各个module
注册到store
对象中&#xff1a;
//index.jsimport Vue from &#39;vue&#39;import Vuex from &#39;vuex&#39;import user from &#39;./modules/user&#39;import team from &#39;./modules/team&#39;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},//严格模式&#xff1a;无论何时发生了状态变更且不是由 mutation 函数引起的&#xff0c;将会抛出错误。这能保证所有的状态变更都能被调试工具跟踪到。strict: true})
然后在main.js
中将store
放进我们的Vue应用程序中&#xff1a;
//main.jsimport Vue from &#39;vue&#39;import App from &#39;./App.vue&#39;import store from &#39;./store&#39;export default new Vue({render: h &#61;> h(App),store}).$mount(&#39;#app&#39;)
至此&#xff0c;这便是一个完整的Vuex的使用实例&#xff0c;虽然在Vue中我们也可以通过属性传递的方式在不同组件之间传递data&#xff0c;但是当同一个data需要被多个组件同时调用&#xff0c;数据的一致性便很难保证&#xff0c;Vuex的引入则很好的解决了这一问题&#xff0c;Vuex中状态的变化是全局的&#xff0c;是实时计算的&#xff0c;当我们getter
的计算依托于多个state
时&#xff0c;当我们提交了新的commit
变更状态&#xff0c;相应的getter
返回值也会变化&#xff0c;这让我们不用过多分心于数据的一致性。