vuex 源码:深刻 vuex 之 module

前言

store 将应用的状态集中起来,但若是应用变得很是复杂时,即状态很是的多时,store 就有可能变得至关臃肿。module 可以帮 store 划分了模块,每一个模块都拥有本身的 state、getter、mutation、action 和 module。vue

那么 module 又是怎样进行划分的,划分后的模块又是如何管理本身的状态呢?接下来就来解读 module 的实现吧。vuex

准备

解读前,须要对如下知识有所了解:api

  1. Array.prototype.reduce()
  2. Vue.set()

解读

在 vuex 文档里有这么一句话:默认状况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的——这样使得多个模块可以对同一 mutation 或 action 做出响应。数组

什么意思呢?先看看如下示例:模块化

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    addNote () {
      console.log('root addNote')
    }
  },
  modules: {
    a: {
      state: {
        count: 0
      },
      mutations: {
        addNote () {
          console.log('module a addNote')
        }
      }
    }
  }
})
复制代码

使用了 module 以后,state 则会被模块化。好比要调用根模块的 state,则调用 store.state.count,若是要调用 a 模块的 state,则调用 store.state.a.countui

可是示例中的 mutation 则是注册在全局下的,即调用 store.commit('addNote'),将会调用跟模块和 a 模块的 mutation。除非区分各模块 mutation 的命名,不然,在同名的状况下,只要 commit 后就会被触发调用。spa

固然,vuex 2.0.0 后面的版本添加了命名空间 的功能,使得 module 更加的模块化。prototype

因此接下来要解读的 module 中,实际上只要 state 是被模块化了, action、mutation 和 getter 仍是在全局的模块下。code

modules 的注册

installModule 里实现了 module 的注册,定位到 installModule 方法。递归

function installModule (store, rootState, path, module, hot) {
  const isRoot = !path.length
  const {
    modules
  } = module

  // set state
  if (!isRoot && !hot) {
    const parentState = getNestedState(rootState, path.slice(0, -1))
    const moduleName = path[path.length - 1]
    store._withCommit(() => {
      Vue.set(parentState, moduleName, state || {})
    })
  }
  
  // mutation 的注册
  // action 的注册
  // getter 的注册

  if (modules) {
    Object.keys(modules).forEach(key => {
      installModule(store, rootState, path.concat(key), modules[key], hot)
    })
  }
}
复制代码

看到简化后的代码,能够看出 installModule 对 module 作了两步初始化操做。第一步是使用 Vue.set() 对当前的 module 的 state 设置了监听;第二步则是继续遍历子模块,而后递归调用 installModule。

set state

因此 modules 的核心实现就在于对当前的 module 的 state 设置了监听,将此段代码提取出来:

const parentState = getNestedState(rootState, path.slice(0, -1))
const moduleName = path[path.length - 1]
store._withCommit(() => {
  Vue.set(parentState, moduleName, state || {})
})
复制代码

先猜想 getNestedState 方法能够获取到父 state。因此先取得父 state,再取得当前模块名称,最后使用 Vue.set() 将当前的 state 设置在父 state 下。实际上该实现就是在一个 vue 实例下为 data.state 添加属性,并可以使得 vue 实例可以监听到添加属性的改动。

getNestedState

const parentState = getNestedState(rootState, path.slice(0, -1))
复制代码

经过 path.slice(0, -1) 将当前模块去掉,做为参数和 rootState 根状态传入 getNestedState 方法中,返回了当前模块的父状态 parentState。

来看看 getNestedState 的实现:

function getNestedState (state, path) {
  return path.length
    ? path.reduce((state, key) => state[key], state)
    : state
}
复制代码

若是 length 等于 0,即只有根 state,直接返回。另外一种状况,若是有嵌套的模块,那么经过 Array.prototype.reduce() 方法一直往根 state 的属性取 path 对应的 state 并返回。

至此,state 的模块化已经注册完成,而后递归调用 installModule 完成全部 module 的注册。

既然是往 rootState 里添加属性,那么获取则能够经过 store.state.a 来获取到模块,而后再继续获取模块里的 state。

get modules state

以前在解读 mutation 和 action 的时候,一直都将 getNestedState 这个方法给省略了。在注册 mutation 和 action 的时候,会出现如下这段代码:

getNestedState(store.state, path)
复制代码

实际上这段代码就是获取当前 modules 的 state,而后做为参数回传。

存放数组

还记得解读 mutation 的时候,说到为何会将 mutation 保存到了 store._mutations 数组里面。主要目的是将全部 module 里的 mutation 都存放在一个数组中,以便于在 commit 的时候能触发全部 mutation。

getter 和 action 用到数组存放也是这样一个缘由。

可是,若是两个 module 里有相同的 mutation 名称,vuex 2.0.0 里作不到只触发其中一个 mutation。这个在日后的版本中设置命名空间可实现。

总结

本篇是对 module 的一个解读。注册 module 并无想象中的那么复杂,主要分为两个步骤。

第一步是找到当前 module 的父 state,而后在其至少绑定当前 state 的监听,保证修改了 state 会触发相应。

第二步则是递归 module,保证设置子 module 的 state,从而实现 module 的子嵌套。

相关文章
相关标签/搜索