vuex是vuejs应用程序专门开发的 状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 也集成到 Vue 的官方调试工具 devtools extension,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。
安装
直接下载/CDN引用
基于NPM的CDN链接(最新版本):https://unpkg.com/vuex
下载后需要在vue之后引入:12<script src="/path/to/vue.js"></script><script src="/path/to/vuex.js"></script>NPM
npm install vuex –save
在一个模块化的打包系统中,您必须显式地通过
Vue.use()来安装 Vuex:1234import Vue from 'vue'import Vuex from 'vuex'Vue.use(Vuex)自己构建
如果需要使用 dev 分支下的最新版本,可以直接从 GitHub 上克隆代码并自己构建git clone https://github.com/vuejs/vuex.git node_modules/vuex
cd node_modules/vuex
npm install
npm run build
基本概念
每一个 Vuex 应用的核心就是store(仓库),store里面存储着大部分的state(状态)。
Vuex 和单纯的全局对象有以下两点不同:
- Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
- 不能直接改变 store 中的状态。改变 store 中的状态的 唯一途径 就是显式地提交(commit) mutations。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。
store
创建store,初始化state对象和一些mutation
现在,我们可以通过 store.state 来获取状态对象,以及通过 store.commit 方法触发状态变更:
核心概念
state
每个应用仅包含一个store(单一状态树),用一个对象包含了全部的应用层级状态。
在vue组件中获取vuex状态
从 store 实例中读取状态最简单的方法就是在计算属性中返回某个状态:123456789// 创建一个 Counter 组件const Counter = {template: `<div>{{ count }}</div>`,computed: {count () {return store.state.count}}}每当
store.state.count变化的时候,都会重新求取计算属性,并且触发更新相关联的 DOM。但是这种模式导致组件依赖的全局状态单例。在模块化的构建系统中,在每个需要使用 state 的组件中需要频繁地导入,并且在测试组件时需要模拟状态。
Vuex 通过 store 选项,提供了一种机制将状态从根组件『注入』到每一个子组件中(需调用
Vue.use(Vuex)):1234567891011const app = new Vue({el: '#app',// 把 store 对象提供给 “store” 选项,这可以把 store 的实例注入所有的子组件store,components: { Counter },template: `<div class="app"><counter></counter></div>`})通过在根实例中注册 store 选项,该 store 实例会注入到根组件下的所有子组件中,且子组件能通过
this.$store访问到。12345678const Counter = {template: `<div>{{ count }}</div>`,computed: {count () {return this.$store.state.count}}}mapState辅助函数
当一个组件需要获取多个状态时候,将这些状态都声明为计算属性会有些重复和冗余。 mapState 辅助函数可以帮助我们生成计算属性,精简代码:1234567891011121314151617181920// 在单独构建的版本中辅助函数为 Vuex.mapStateimport { mapState } from 'vuex'export default {// ...computed: mapState({// 箭头函数可使代码更简练// 当计算属性名称和state的子节点名称相同时,也可以向mapState传递一个字符串数组// 如:'count'count: state => state.count,// 传字符串参数 'count' 等同于 `state => state.count`countAlias: 'count',// 为了能够使用 `this` 获取局部状态,必须使用常规函数countPlusLocalState (state) {return state.count + this.localCount}})}对象展开运算符
mapState函数返回的是一个对象。对象展开运算符可以讲多个对象合并为一个,方便传递最终对象给computed属性1234567computed: {localComputed () { /* ... */ },// 使用对象展开运算符将此对象混入到外部对象中...mapState({// ...})}
getters
Vuex 允许我们在 store 中定义『getters』(可以认为是 store 的计算属性)。Getters 接受 state 作为其第一个参数:
Getters 会暴露为store.getters对象:
Getters 也可以接受其他 getters 作为第二个参数:
这样我们就能在任何组件中使用getters了:
mapGetters辅助函数
mapGetters 辅助函数仅仅是将 store 中的 getters 映射到局部计算属性:12345678910111213141516import { mapGetters } from 'vuex'export default {// ...computed: {// 使用对象展开运算符将 getters 混入 computed 对象中...mapGetters([// 如果你想将一个 getter 属性另取一个名字,使用对象形式// 映射 this.doneCount 为 store.getters.doneTodosCount// doneCount: 'doneTodosCount''doneTodosCount','anotherGetter',// ...])}}
mutations
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。
每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数:
要唤醒一个 mutation handler,需要以相应的 type 调用 store.commit 方法:
提交荷载
可以向 store.commit 传入额外的参数,即 mutation 的载荷(payload):12345678// ...mutations: {increment (state, n) {state.count += n}}store.commit('increment', 10)当荷载为对象时,可以包含多个字段:
12345678910// ...mutations: {increment (state, payload) {state.count += payload.amount}}store.commit('increment', {amount: 10})对象风格的提交方式
提交 mutation 的另一种方式是直接使用包含 type 属性的对象:1234store.commit({type: 'increment',amount: 10})当使用对象风格的提交方式,整个对象都作为载荷传给 mutation 函数,因此 handler 保持不变:
12345mutations: {increment (state, payload) {state.count += payload.amount}}Mutations 需遵守 Vue 的响应规则
mutaion需要遵循vue的响应式原则:
1.最好提前在你的 store 中初始化好所有所需属性。
2.当需要在对象上添加新属性时,你应该:- 使用 Vue.set(obj, ‘newProp’, 123), 或者
- 以新对象替换老对象。例如,利用 stage-3 的对象展开运算符可以这样写:1state.obj = { ...state.obj, newProp: 123 }
mutation 必须是同步函数
实例:1234567mutations: {someMutation (state) {api.callAsyncMethod(() => {state.count++})}}现在想象,我们正在 debug 一个 app 并且观察 devtool 中的 mutation 日志。每一条 mutation 被记录,devtools 都需要捕捉到前一状态和后一状态的快照。然而,在上面的例子中 mutation 中的异步函数中的回调让这不可能完成:因为当 mutation 触发的时候,回调函数还没有被调用,devtools 不知道什么时候回调函数实际上被调用 —— 实质上任何在回调函数中进行的的状态的改变都是不可追踪的。
在组件中提交 Mutations
可以在组件中使用this.$store.commit('xxx')提交 mutation,或者使用 mapMutations 辅助函数将组件中的 methods 映射为 store.commit 调用(需要在根节点注入 store)。12345678910111213import { mapMutations } from 'vuex'export default {// ...methods: {...mapMutations(['increment' // 映射 this.increment() 为 this.$store.commit('increment')]),...mapMutations({add: 'increment' // 映射 this.add() 为 this.$store.commit('increment')})}}
actions
Action 类似于 mutation,不同在于:
- Action 提交的是 mutation,而不是直接变更状态。
Action 可以包含任意异步操作
示例:123456789101112131415const store = new Vuex.Store({state: {count: 0},mutations: {increment (state) {state.count++}},actions: {increment (context) {context.commit('increment')}}})Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters。
分发Action
Action 通过 store.dispatch 方法触发:1store.dispatch('increment')在 action 内部执行异步操作
1234567actions: {incrementAsync ({ commit }) {setTimeout(() => {commit('increment')}, 1000)}}Actions 支持同样的 载荷方式 和 对象方式 进行分发
在组件中分发 Action
在组件中使用this.$store.dispatch('xxx')分发 action,或者使用 mapActions 辅助函数将组件的 methods 映射为 store.dispatch 调用(需要先在根节点注入 store):12345678910111213import { mapActions } from 'vuex'export default {// ...methods: {...mapActions(['increment' // 映射 this.increment() 为 this.$store.dispatch('increment')]),...mapActions({add: 'increment' // 映射 this.add() 为 this.$store.dispatch('increment')})}}组合 Actions
详情参见(https://vuex.vuejs.org/zh-cn/actions.html)
modules
详情参见Modules
项目结构
vuex结构需要遵循一些规则:
1.应用层级的状态应该集中到单个 store 对象中。
2.提交 mutation 是更改状态的唯一方法,并且这个过程是同步的
3.异步逻辑都应该封装到 action 里面。
只要你遵守以上规则,如何组织代码随你便。如果你的 store 文件太大,只需将 action、mutation、和 getters 分割到单独的文件Modules。
对于大型应用,我们会希望把 Vuex 相关代码分割到模块中。下面是项目结构示例: