共计 5597 个字符,预计需要花费 14 分钟才能阅读完成。
一、前言
当我们的应用遇到多个组件共享状态时,会需要多个组件依赖于同一状态抑或是来自不同视图的行为需要变更同一状态。以前的解决办法:
a. 将数据以及操作数据的行为都定义在父组件;
b. 将数据以及操作数据的行为传递给需要的各个子组件(有可能需要多级传递)
传参的方法对于多层嵌套的组件将会非常繁琐,并且对于兄弟组件间的状态传递无能为力。在搭建下面页面时,你可能会对 vue 组件之间的通信感到崩溃,特别是非父子组件之间通信。此时就应该使用 vuex,轻松可以搞定组件间通信问题。
二、什么是 Vuex
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。这里的关键在于集中式存储管理。简单来说, 对 vue 应用中多个组件的共享状态进行集中式的管理(读 / 写)。
三、Vuex 的原理是什么
1. 简要介绍 Vuex 原理
Vuex 实现了一个单向数据流,在全局拥有一个 State 存放数据,当组件要更改 State 中的数据时,必须通过 Mutation 进行,Mutation 同时提供了订阅者模式供外部插件调用获取 State 数据的更新。而当所有异步操作 (常见于调用后端接口异步获取更新数据) 或批量的同步操作需要走 Action,但 Action 也是无法直接修改 State 的,还是需要通过 Mutation 来修改 State 的数据。最后,根据 State 的变化,渲染到视图上。
2. 简要介绍各模块在流程中的主要功能:
Vue Components:Vue 组件。HTML 页面上,负责接收用户操作等交互行为,执行 dispatch 方法触发对应 action 进行回应。
dispatch:操作行为触发方法,是唯一能执行 action 的方法。
actions:操作行为处理模块, 由组件中的 $store.dispatch(‘action 名称 ’, data1)来触发。然后由 commit()来触发 mutation 的调用 , 间接更新 state。负责处理 Vue Components 接收到的所有交互行为。包含同步 / 异步操作,支持多个同名方法,按照注册的顺序依次触发。向后台 API 请求的操作就在这个模块中进行,包括触发其他 action 以及提交 mutation 的操作。该模块提供了 Promise 的封装,以支持 action 的链式触发。
commit:状态改变提交操作方法。对 mutation 进行提交,是唯一能执行 mutation 的方法。
mutations:状态改变操作方法,由 actions 中的 commit(‘mutation 名称 ’)来触发。是 Vuex 修改 state 的唯一推荐方法。该方法只能进行同步操作,且方法名只能全局唯一。操作之中会有一些 hook 暴露出来,以进行 state 的监控等。
state:页面状态管理容器对象。集中存储 Vue components 中 data 对象的零散数据,全局唯一,以进行统一的状态管理。页面显示所需的数据从该对象中进行读取,利用 Vue 的细粒度数据响应机制来进行高效的状态更新。
getters:state 对象读取方法。图中没有单独列出该模块,应该被包含在了 render 中,Vue Components 通过该方法读取全局 state 对象。
四、什么时候使用 Vuex
虽然 Vuex 可以帮助我们管理共享状态,但也附带了更多的概念和框架。这需要对短期和长期效益进行权衡。如果您的应用够简单,您最好不要使用 Vuex, 因为使用 Vuex 可能是繁琐冗余的。一个简单的 global event bus 就足够您所需了。但是,如果您需要构建一个中大型单页应用,您很可能会考虑如何更好地在组件外部管理状态,Vuex 将会成为自然而然的选择。
五、Vuex 安装(限定开发环境为 vue-cli)
首先要安装 vue-cli 脚手架,对于大陆用户,建议将 npm 的注册表源设置为国内的镜像(淘宝镜像),可以大幅提升安装速度。
npm config set registry https://[registry.npm.taobao.org](http://registry.npm.taobao.org/)
npm config get registry// 配置后可通过下面方式来验证是否成功
npm install -g cnpm –registry=[https://registry](https://registry/).npm.taobao.org
//cnpm 安装脚手架
cnpm install -g vue-cli
vue init webpack my-vue
cd my-vue
cnpm install
cnpm run dev
脚手架安装好后,再安装 vuex
cnpm install vuex –save
六、如何使用 Vuex
### 1. 如何通过 Vue 来实现如下效果?
这个小 demo 很容易用 vue 实现,核心代码如下:
<div class=”hello”>
<p>click {{count}} times,count is {{evenOrOdd}}</p>
<button @click=”increment”>+</button>
<button @click=”decrement”>-</button>
<button @click=”incrementIfOdd”>increment if odd</button>
<button @click=”incrementAsync”>increment async</button>
</div>
……
export default {
name: “HelloWorld”,
data() {
return {
count: 0
};
},
computed: {
evenOrOdd() {
return this.count % 2 === 0 ? “ 偶数 ” : “ 奇数 ”;
}
},
methods: {
increment() {
this.count = this.count + 1;
},
decrement() {
this.count = this.count – 1;
},
// 只有是奇数才加 1
incrementIfOdd() {
if (this.count % 2 === 1) {
this.count = this.count + 1;
}
},
// 过两秒才加 1
incrementAsync() {
setInterval(() => {
this.count = this.count + 1;
}, 2000);
}
}
}
2. 如何通过 Vuex 来改造上面代码?
①创建一个 store.js 文件
import Vue from ‘vue’
import Vuex from ‘vuex’
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {// 包含了多个直接更新 state 函数的对象
INCREMENT(state) {
state.count = state.count + 1;
},
DECREMENT(state) {
state.count = state.count – 1;
}
},
getters: {// 当读取属性值时自动调用并返回属性值
evenOrOdd(state) {
return state.count % 2 === 0 ? “ 偶数 ” : “ 奇数 ”;
}
},
actions: {// 包含了多个对应事件回调函数的对象
incrementIfOdd({commit, state}) {// 带条件的 action
if (state.count % 2 === 1) {
commit(‘INCREMENT’)
}
},
incrementAsync({commit}) {// 异步的 action
setInterval(() => {
commit(‘INCREMENT’)
}, 2000);
}
}
})
export default store // 用 export default 封装代码,让外部可以引用
②在 main.js 文件中引入 store.js 文件
import store from ‘./store’
new Vue({
el: ‘#app’,
router,
store,// 注册上 vuex 的 store: 所有组件对象都多一个属性 $store
components: {App},
template: ‘<App/>’
})
③新建一个模板 HelloWorld.vue
<template>
<div class=”hello”>
<p>click {{count}} times,count is {{evenOrOdd}}</p>
<button @click=”increment”>+</button>
<button @click=”decrement”>-</button>
<button @click=”incrementIfOdd”>increment if odd</button>
<button @click=”incrementAsync”>increment async</button>
</div>
</template>
<script>
export default {
name: “HelloWorld”,
computed: {
count() {
return this.$store.state.count;
},
evenOrOdd() {
return this.$store.getters.evenOrOdd;
}
},
methods: {
increment() {
this.$store.commit(“INCREMENT”);
},
decrement() {
this.$store.commit(“DECREMENT”);
},
// 只有是奇数才加 1
incrementIfOdd() {
this.$store.dispatch(“incrementIfOdd”); // 触发 store 中对应的 action 调用
},
// 过两秒才加 1
incrementAsync() {
this.$store.dispatch(“incrementAsync”);
}
}
};
</script>
由于 store 中的状态是响应式的,当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。在组件中调用 store 中的状态简单到仅需要在计算属性中返回即可。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutations。
3. 如何通 mapState 等辅助函数优化上面代码?
import {mapActions, mapGetters, mapState, mapMutations} from “vuex”;
…
computed: {
…mapState([“count”]),
…mapGetters([“evenOrOdd”])
}
methods: {
…mapActions([“incrementIfOdd”, “incrementAsync”]),
…mapMutations([“increment”, “decrement”])
}
有点必须要注意:HelloWorld.vue 文件中 increment 函数名称要跟 store.js 文件 mutations 中一致,才可以写成 …mapMutations([“increment”, “decrement”]),同样的道理,incrementIfOdd 和 incrementAsync 也要和 store.js 文件 actions 保持一致。
七、使用 Vuex 的注意点
1. 如何在 Mutations 里传递参数
先 store.js 文件里给 add 方法加上一个参数 n
mutations: {
INCREMENT(state,n) {
state.count+=n;
},
DECREMENT(state){
state.count–;
}
}
然后在 HelloWorld.vue 里修改按钮的 commit()方法传递的参数
increment() {
return this.$store.commit(“INCREMENT”,2);
},
decrement() {
return this.$store.commit(“DECREMENT”);
}
2. 如何理解 getters
getters 从表面是获得的意思,可以把他看作在获取数据之前进行的一种再编辑, 相当于对数据的一个过滤和加工。getters 就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
例如:要对 store.js 文件中的 count 进行操作,在它输出前,给它加上 100。
首先要在 store.js 里 Vuex.Store()里引入 getters
getters:{
count:state=>state.count+=100
}
然后在 HelloWorld.vue 中对 computed 进行配置,在 vue 的构造器里边只能有一个 computed 属性,如果你写多个,只有最后一个 computed 属性可用,所以要用展开运算符”…”对上节写的 computed 属性进行一个改造。
computed: {
…mapGetters([“count”])
}
3.actions 和 mutations 区别
actions 和上面的 Mutations 功能基本一样,不同点是,actions 是异步的改变 state 状态,而 Mutations 是同步改变状态。
同步的意义在于这样每一个 mutation 执行完成后都可以对应到一个新的状态(和 reducer 一样),这样 devtools 就可以打个 snapshot 存下来,然后就可以随便 time-travel 了。如果你开着 devtool 调用一个异步的 action,你可以清楚地看到它所调用的 mutation 是何时被记录下来的,并且可以立刻查看它们对应的状态 —- 尤雨溪
ps: 如果想访问源代码,请猛戳 git 地址
如果觉得文章对你有些许帮助,欢迎在我的 GitHub 博客点赞和关注,感激不尽!
参考文章
vuex 官方文档
Vuex 2.0 源码分析