Vuex
-
Vuex是什么:
状态:Vue组件中的data里的数据
Vuex是一款专门为Vue.js设计的状态管理模式,它采用集中式的状态存储管理,并以相应的规则保证状态以一种可预测的方式发生变化。
-
为什么需要Vuex:
-
当我们的应用中有多个不同视图的要依赖同一状态时
-
当有多个不同的视图要对同一状态进行修改时
第一种情况,多个组件要依赖同一状态,可能需要多重的嵌套,十分繁琐
第二种情况,当多个组件对同一个状态进行修改时,可能通过事件或者父子组件直接饮用来修改,这会让代码后期很难确定哪个组件修改了状态,让代码难以维护
因此,此时就需要将共享变量抽离出来,用全局单例的Vuex来集中式的管理一些组件要使用到的共享变量
-
-
如何使用Vuex:
快速开始
-
安装使用:
-
npm install vuex --save
-
import Vue from ‘vue’
import Vuex from ‘vuex’Vue.use(Vuex)
-
-
简易的demo
-
创建Vuex的store
import Vue from 'vue' import Vuex from 'vuex' const store = new Vuex.Store({ state: { count: 0 }, mutations: { increment (state) { state.count++ } } }) //创建一个store,存入Vuex中的共享状态都在store.state中,motations中的方法用于修改state
-
获取store中的数据
console.log(store.state.count) // -> 0
-
修改store中的数据
store.commit('increment') console.log(store.state.count) // -> 1
-
Vuex的核心概念
每一个 Vuex 应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的状态 (state)。Vuex 和单纯的全局对象有以下两点不同:
- Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
- 不可以直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。
核心概念
-
State
Vuex 使用单一状态树——是的,用一个对象就包含了全部的应用层级状态。至此它便作为一个“唯一数据源 ”而存在。这也意味着,每个应用将仅仅包含一个 store 实例。单一状态树让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。
-
如何获取Vuex中的状态
由于 Vuex 的状态存储是响应式的,从 store 实例中读取状态最简单的方法就是在computed中返回某个状态:
// 在根组件中注册store选项,避免后续需要频繁的注入store const app = new Vue({ el: '#app', // 把 store 对象提供给 “store” 选项,这可以把 store 的实例注入所有的子组件 store, components: { Counter }, template: ` <div class="app"> <counter></counter> </div> ` }) // 创建一个 Counter 组件 const Counter = { template: `<div>{{ count }}</div>`, computed: { count () { return this.$store.state.count } } }
-
mapState辅助函数(每个Vuex的核心都有一个辅助函数,帮助减少代码量)
当一个组件需要获取多个状态的时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性
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映射到组件的data中 'count' ])
-
-
Getter
有时候我们需要对stroe中的状态进行一些筛选转换等操作,比如:
computed: { doneTodosCount () { return this.$store.state.todos.filter(todo => todo.done).length } }
如果有多个组件需要用到这个属性,那么我们就需要在每个组件中复制这个函数或者抽取一个共享函数出来,略有些麻烦
因此,Vuex设计了Getter(geteer可以理解是stroe的computed属性)供我们使用,就像computed一样,getter 的返回值会根据被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
//定义getter 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) } } }) //访问getter状态 store.getters.doneTodos // -> [{ id: 1, text: '...', done: true }]
//getter也可以在第二个参数接收其他的getter getters: { // ... doneTodosCount: (state, getters) => { return getters.doneTodos.length } } store.getters.doneTodosCount // -> 1
//还可以让getter返回一个函数,来实现给getter传参 getters: { // ... getTodoById: (state) => (id) => { return state.todos.find(todo => todo.id === id) } } store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false } //getter 在通过方法访问时,每次都会去进行调用,而不会缓存结果。
-
mapGetters 辅助函数
//mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性: import { mapGetters } from 'vuex' export default { // ... computed: { // 使用对象展开运算符将 getter 混入 computed 对象中 ...mapGetters([ 'doneTodosCount', 'anotherGetter', // ... ]) } }
-
-
Mutaition
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation(store.commit(‘mutation’))。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数:
//定义mutation const store = new Vuex.Store({ state: { count: 1 }, mutations: { increment (state) { // 变更状态 state.count++ } } })
不能直接调用一个 mutation handler。这个选项更像是事件注册:“当触发一个类型为
increment
的 mutation 时,调用此函数。”要唤醒一个 mutation handler,需要调用 store.commit 方法:store.commit('increment')
// 向mutation传入参数,这个参数称为“载荷”(Payload) // 在大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读 mutations: { increment (state, payload) { state.count += payload.amount } } store.commit('increment', { amount: 10 })
- 对象风格的提交方式:
提交 mutation 的另一种方式是直接使用包含
type
属性的对象,vuex会根据type找到对应mutation:store.commit({ type: 'increment', amount: 10 })
当使用对象风格的提交方式,整个对象都作为载荷传给 mutation 函数,因此 handler 保持不变:
mutations: { increment (state, payload) { state.count += payload.amount } }
- mapMutations
import { mapMutations } from 'vuex' export default { // ... methods: { ...mapMutations([ 'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')` // `mapMutations` 也支持载荷: 'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)` ]), ...mapMutations({ add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')` }) } }
-
Mutation 必须是同步函数
mutation如果是异步的,当 mutation 触发的时候,回调函数还没有被调用,Vuex 不知道什么时候回调函数实际上被调用,导致Vuex的状态的变成不可追踪。
所以Mutation必须是同步的
-
Action
Action 类似于 mutation,不同在于:
-
Action 提交的是 mutation,而不是直接变更状态(在action的中提交mutation)。
-
Action 可以包含任意异步操作(在action的异步方法中提交mutation)。
const store = new Vuex.Store({ state: { count: 0 }, mutations: { increment (state) { state.count++ } }, actions: { increment (context) { context.commit('increment') } } }) //实践中,我们会经常用到 ES2015 的 参数解构来简化代码(特别是我们需要调用 commit 很多次的时候): actions: { increment ({ commit }) { commit('increment') } }
//Action 通过 store.dispatch 方法触发: store.dispatch('increment')
- Action的异步操作
actions: { checkout ({ commit, state }, products) { // 把当前购物车的物品备份起来 const savedCartItems = [...state.cart.added] // 发出结账请求,然后乐观地清空购物车 commit(types.CHECKOUT_REQUEST) // 购物 API 接受一个成功回调和一个失败回调 shop.buyProducts( products, // 成功操作 () => commit(types.CHECKOUT_SUCCESS), // 失败操作 () => commit(types.CHECKOUT_FAILURE, savedCartItems) ) } }
- 组合Action
store.dispatch
可以处理被触发的 action 的处理函数返回的 Promise,并且store.dispatch
仍旧返回 Promise//注册Action actions: { actionA ({ commit }) { return new Promise((resolve, reject) => { setTimeout(() => { commit('someMutation') resolve() }, 1000) }) } } //在组件中调用 store.dispatch('actionA').then(() => { // ... }) //也可以在另一个action中调用 actions: { // ... actionB ({ dispatch, commit }) {//传入dispatch return dispatch('actionA').then(() => { commit('someOtherMutation') }) } }
//如果我们利用 async / await,我们可以如下组合 action: // 假设 getData() 和 getOtherData() 返回的是 Promise actions: { async actionA ({ commit }) { commit('gotData', await getData()) }, async actionB ({ dispatch, commit }) { await dispatch('actionA') // 等待 actionA 完成 commit('gotOtherData', await getOtherData()) } }
-
-
Module
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
为了解决以上问题,Vuex可以 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:
const moduleA = { state: () => ({ ... }), //这里的state是方法 mutations: { ... }, actions: { ... }, getters: { ... } } const moduleB = { 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: { sumWithRootCount (state, getters, rootState) { return state.count + rootState.count } } }
- 命令空间
默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutation 或 action 作出响应。
如果希望你的模块具有更高的封装度和复用性,你可以通过添加
namespaced: true
的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。例如:const store = new Vuex.Store({ modules: { account: { namespaced: true, // 模块内容(module assets) state: () => ({ ... }), // 模块内的状态已经是嵌套的了,使用 `namespaced` 属性不会对其产生影响 getters: { isAdmin () { ... } // -> getters['account/isAdmin'] }, actions: { login () { ... } // -> dispatch('account/login') }, mutations: { login () { ... } // -> commit('account/login') }, // 嵌套模块 modules: { // 继承父模块的命名空间 myPage: { state: () => ({ ... }), getters: { profile () { ... } // -> getters['account/profile'] } }, // 进一步嵌套命名空间 posts: { namespaced: true, state: () => ({ ... }), getters: { popular () { ... } // -> getters['account/posts/popular'] } } } } } })
-
在带命名空间的模块内访问全局内容(Global Assets)
如果你希望使用全局 state 和 getter,rootState 和 rootGetters 会作为第三和第四参数传入 getter,也会通过 context 对象的属性传入 action。
若需要在全局命名空间内分发 action 或提交 mutation,将 { root: true } 作为第三参数传给 dispatch 或 commit 即可。
modules: { foo: { namespaced: true, getters: { // 在这个模块的 getter 中,`getters` 被局部化了 // 你可以使用 getter 的第四个参数来调用 `rootGetters` someGetter (state, getters, rootState, rootGetters) { getters.someOtherGetter // -> 'foo/someOtherGetter' rootGetters.someOtherGetter // -> 'someOtherGetter' }, someOtherGetter: state => { ... } }, actions: { // 在这个模块中, dispatch 和 commit 也被局部化了 // 他们可以接受 `root` 属性以访问根 dispatch 或 commit someAction ({ dispatch, commit, getters, rootGetters }) { getters.someGetter // -> 'foo/someGetter' rootGetters.someGetter // -> 'someGetter' dispatch('someOtherAction') // -> 'foo/someOtherAction' dispatch('someOtherAction', null, { root: true }) // -> 'someOtherAction' commit('someMutation') // -> 'foo/someMutation' commit('someMutation', null, { root: true }) // -> 'someMutation' }, someOtherAction (ctx, payload) { ... } } } }
-