安装 直接下载CDN 引用 <script src="/path/to/vue.js"></script> <script src="/path/to/vuex.js"></script> npm npm install vuex --save 在一个模块化的打包系统中,您必须显式地通过Vue.use() 来安装Vuex。 import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) Vuex 是一个专为Vue.js 应用程序开发 的状态管理模式,集中式存储管理应用的所有组件状态。 状态管理包含以下几个部分状态: state 驱动应用的数据源; view 以生命方式将 state 映射到视图。 actions 响应在view 上的用户书输入导致的状态变化。 帮助我们管理共享状态,中大型单页面应用。 state 单一状态树 ,Vuex使用单一状态树用一个对象就包含了全部的应用层级状态。 在Vue 组件中获得Vuex 状态。 由于Vuex的状态存储是响应式的,从store 实例中读取状态最简单的方法 就是在计算属性中返回某个状态。 创建一个Counter 组件 const Counter = { template: '<div>{{ count }}</div>' computed: { count (){ return store.state.count } } } 每次 store.state.count 变化的时候,都会重新求取计算属性,并且触发更 新相关的DOM Vuex 通过 store 选项,提供了一种机制将状态从根组件『注入』到每一个子组件 中(需调用 Vue.use(Vuex)): const app = new Vue({ el:'#app', // 把 store 对象提供给 “store” 选项,这可以把 store 的实例注入所 有的子组件 store, components: {Counter}, template: ' <div class="app"> <counter></counter> </div> ' }) 通过在根实例中注册 store 选项,该store 实例会注册入到跟组件下的所有 子组件,且子组件能通过 this.$store 访问到。更新 counter 的实现: const Counter = { template : '<div>{{ count }}</div>', computed: { count this.$store.state.count } } mapState 辅助函数 当一个组件需要获取多个状态时候,将这些状态都声明为计算属性会有些冗余。 为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性。 // 在单独构建的版本中辅助函数为 Vuex.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 } }) } 当映射的计算属性的名称与 state 的子节点名称相同时,我们也 可以给 mapState 传一个字符串数组。 computed: mapState([ // 映射 this.count 为 store.state.count 'count' ]) 组件仍然保有局部状态。 Getters 有时候我们需要从store 中的state 中 的state 中派生一些状态,列如对列表进 行过滤并计算。 computed: { doneTodosCount() { return this.$store.state.todos.filter(todo => todo.done).length } } Vuex 允许我们再store 中定义getters (可以认为是stroe 的计算属性) Getters 接受state 作为其第一个参数。 const store = new Vuex.Store({ state: { todos:[ {id:1, text: '...' ,done: true}, {id:2,text:'...',done: false} ] }, getters: { doneTodos: state => { return state.todos.filter(todo=> todo.done) } } }) Getters 会暴露为store.getters 对象: store.getters.doneTodos // [{id:1,text: '...',done:true}] Getter 也可以接受其他getters 作为第二个参数: getters: { doneTodosCount: (state,getters) => { return getters.doneTodos.length } } store.getters.doneTodosCount // -> 1 我们可很容易的在任何组件中使用 computed: { doneTodosCount() { return this.$store.getters.doneTodosCount } } mapGetters 辅助函数 mapGetters 辅助函数仅仅是 store 中的getters 映射到局部计算属性。 import {mapGetter} form 'vuex' export default { computed: { // 使用对象展开运算符将 getters 混入 ...mapGetters([ ‘doneTodosCount’, 'anotherGetter' ]) } } 如果你想讲一个getter 属性另取名字,使用对象性时 mapGetters({ // 映射 this.doneCount 为 store.getters.doneTodosCount doneCount: 'doneTodosCount' }) Mutations 更改Vuex 的store 中的状态的唯一方式就是提交 mutation Vuex 中的mutation 非常类似于事件,每个 mutation 都有一个字符串的 事件类型 和回调函数。这个 回调函数就是我们实际进行状态更改的地方。并且他会接受 state 作为第一个参数。 const store = new Vue.Store({ state: { count: 1 }, mutations: { inctement (state) { state.count++ } } }) 当触发一个类型为 increment 的 mutation 时,调用此函数。”要唤醒一个 mutation handler,你需要以相应的 type 调用 store.commit 方法 store.commit('increment') 提交载荷(Payload) 你可以向store.commit 传入额外的参数,即mutation 的载荷: mutations: { increment (state, n) { state.count += n } } store.commit('increment', 10) 在大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录 mutation会更易读。 mutations: { increment (state,payload) { state.count += payload.amount } } store.commit('increment', { amount:10 }) 对象风格的提交方式 提交mutation 的另一种方式直接使用包含 type 属性的对象: store.commit({ type: 'increment', amount:10 }) 当使用对象风格的提交方式,整个对象作为载荷传给mutation 函数,因此handler保持不变: mutations: { increment (state, payload) { state.count += payload.amount } } Mutations 需遵守vue 的响应规则 既然Vuex的store 中的状态是响应式的,那么当我们变更状态时,监视状态的vue更新 ,这也意味值Vue 中的mutation 也需要与使用 Vue 一样遵守一些注意事项。 1. 最好提前在你的store 中初始化好所有的所需要的属性。 2.当需要在对象上提交新属性时,你应该使用 Vue.set(obj, 'newProp', 123) 使用新对象代替老对象 state.obj= {...state.obj ,newProp: 123} 使用常量替代 Mutation 事件类型 使用常量替代 mutation 事件类型在各种 Flux 实现中是很常见的模式 export const SOME_MUTATION = 'SOME_MUTATION'; import Vuex from 'vuex' import {SOME_MUTATION } from './mutation-types' const store = new Vuex.Store({ state: {...} mutations: { // 我们可以使用 ES2015 风格的计算属性命名功能来使用一个常量作为函数名 [SOME_MUTATION] (state) { // mutate state } } }) mutation 必须是同步函数 一条重要的原则是记住 mutation 必须是同步函数。 mutations: { someMutation (state) { api.callAsyncMethod(() => { state.count++ }) } } 在组件中提交 Mutations 你可以在组件中使用 this.$store.commit('xxx') 提交 mutation,或者使使用 mapMutations辅助函数将组建中的methods 映射为 store.commit 调用 (需要在根节点注入 store) import {mapMutations} from 'vuex' expor default { methods: { mapMutations([ methods: { mapMutations([ 'increment' // 映射 this.increment() 为 this.$store.commit('increment') ]), mapMutations({ add: 'increment' // 映射 this.add() 为 this.$store.commit('increment') }) } ]) } } Actions 在mutation 中混异步调用会导致你的程序很难调试。 Actions Action 类似于 mutation,不同在于。 Action 提交的是 mutation ,而不是直接变更状态。 Action 可以包含任意异步操作。 注册一个简单的 action const 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 当我们在之后介绍到Modules时, 你就知道 context 对象为什么不是store 实例本身了。 actions: { increment({commit}){ commit('increment') } } 分发 Action Action 通过 store.dispatch 方法触发: store.dispatch('increment') 我们可以在 action 内部执行异步操作。 actions: { incrementAsync({commit}){ setTimeout(() => { commit('increment') },1000) } } Actions 支持同样的载荷方式和对象方式进行分发 // 以载荷形式分发 store.dispatch('incrementAsync',{ amount:10 }) // 以对象形式分发 store.dispatch({ type: 'incrementAsync', amount:10 }) 在组件中分发 Action 你在组件中使用 this.$store.dispatch('xxx') 分发 action,或者使用map Actions辅助函数将组件的methods 映射为store.dispatch 调用 import {mapActions } from 'vuex' export default{ methods:([ 'increment' // 映射 this.increment() 为 this.$store.dispatch('increment') ]) mapActions({ add: 'inctement' // 映射 this.add() 为 this.$store.dispatch('increment') }) } 组合 Actions Action 通常是异步的,那么如何知道 action 什么时候结束。 你需要明白 store.dispatch 可以处理被处触发的action 的回调函数返回的Promise 并且 store.dispatch 仍旧返回Promise actions: { actionA({commit}){ return new Promise((resolve)=>{ setTimeout (() => { commit('someMutation') resolve() },1000) }) } } 现在你可以 store.dispatch('actionA').then(()=>{ //... }) 在另一个 action 中也可以 actions: { actionB({dispath,commit}){ return dispatch('actionA').then(() => { commit('someOtherMutation') }) } } 我们利用async/ await // 假设 getData() 和 getOther() 返回的是一个 Promis actions:{ async actionA ({commit}){ commit('gotData',await getData()) }, async actionB({dispatch,commit}){ await dispatch('actionA') // 等待 actionA 完成 commit('goOtherData', await getOtherData()) } } Modules 使用单一状态树,当应用变的很大的时候,store 对象会变的臃肿不堪。 Vuex 允许我们将store 分割到模块。每一个模块都有自己的state, mutation,action, getters, 甚至是嵌套子模块从上到下进行类似的分割。 const moduleA = { state: {...}, mutations: {...} actions: {...} getters:{...} } const moduleA = { state: {...}, mutations: {...} actions: {...} } const store = new Vuex.Store({ modules: { a:moduleA, b:moduleB } }) store.state.a // -> moduleA 的状态 store.state.b // -> moduleB 的状态 模块的局部状态 对于模块内部的 mutation 和 getter, 接收的第一个参数是模块的局部状态。 const moduleA = { state: {count:0}, mutations: { increment (state) { // state 模块的局部状态 state.count++ } }, getters: { doubleCount (state) { return state.count * 2 } } } 同样对于模块内部的action, context.state 是局部状态,根节点的窗台石context.rootState: const moduleA = { actions: { incrementIfOddOnRootSum ({state, commit ,rootState}) { if((state.count + rootState.count) %2 ===1){ commit('increment') } } } } 对于模块内部的getter,跟节点状态会作为第三个参数: const moduleA = { getters: { getters: { sumWithRootCount (state,getters,rootState) { return state.count + rootState.count } } } } 命名空间 模块内部的action, mutation , 和 getter 现在仍然注册在全局命名空间 这样保证了多个模块能够响应同一 mutation 或 action. 也可以通过添加前缀 或者 后缀的 方式隔离各个模块,以免冲突。 // 定义 getter, action , 和 mutation 的名称为常量,以模块名 ‘todo’ 为前缀。 export const DONE_COUNT = 'todos/DONE_COUNT' export const FETCH_ALL = 'todos/FETCH_ALL' export const TOGGLE_DONE = 'todos/TOGGLE_DONE' import * as types form '../types' // 使用添加了解前缀的名称定义, getter, action 和 mutation const todosModule = { state : {todo: []}, getters: { [type.DONE_COUNT] (state) { } } actions: { [types.FETCH_ALL] (context,payload) { } }, mutations: { [type.TOGGLE_DONE] (state, payload) } } 模块动态注册 在store 创建之后,你可以使用 store.registerModule 方法注册模块。 store.registerModule('myModule',{}) 模块的状态将是 store.state.myModule. 模块动态注册功能可以使让其他Vue 插件为了应用的store 附加新模块 以此来分割Vuex 的状态管理。 项目结构 Vuex 并不限制你的代码结构。但是它规定了一些需要遵守的规则: 1.应用层级的状态应该集中到单个store 对象中。 2.提交 mutation 是更改状态的唯一方法,并且这个过程是同步的。 3.异步逻辑应该封装到action 里面。 只要你遵守以上规则,如何组织代码随你便。如果你的 store 文件太大, 只需将 action、mutation、和 getters 分割到单独的文件 对于大型应用,我们会希望把 Vuex 相关代码分割到模块中。下面是项目结构示例 ├── index.html ├── main.js ├── api │ └── ... # 抽取出API请求 ├── components │ ├── App.vue │ └── ... └── store ├── index.js # 我们组装模块并导出 store 的地方 ├── actions.js # 根级别的 action ├── mutations.js # 根级别的 mutation └── modules ├── cart.js # 购物车模块 └── products.js # 产品模块