vuex 源码:深入 vuex 之 module

前言

store 将应用的状态集中起来,但如果应用变得非常复杂时,即状态非常的多时,store 就有可能变得相当臃肿。module 能够帮 store 划分了模块,每个模块都拥有自己的 state、getter、mutation、action 和 module。

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

准备

解读前,需要对以下知识有所了解:

  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.count

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

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

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

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 的子嵌套。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值