Vuex源码学习(八)模块的context如何被创建以及它的作用

1,026 阅读6分钟

你不知道action与mutation怎么被调用的?赶紧回去看啊Vuex源码学习(七)action和mutation如何被调用的(调用篇))

上两个小节已经讲述了commit与dispatch如何调用mutation与action的,但是action中有几个参数感觉涉及到了一些我们遗漏(故意不讲)的点。

模块的context

在installModule的时候 给每个模块绑定了一个属性context。 通过makeLocalContext函数创建的,在注册action、mutation和getters都有使用。这个context是什么呢?

makeLocalContext函数创建了一个什么东西

返回值local对象 由两个方法、两个属性构成的。

这个目的是什么?创建局部模块的dispatch、commit、getters、state

也就是这个东西

我们按照类型分析

  1. dispatch与commit
// 查看全名,如果没有全名 可能是根模块或者没有设置命名空间
const noNamespace = namespace === '';
// 如果没有全名 就使用全局(store)上的disptach
// 有全名的话 构建一个新的dispatch 
// 这个新的dispatch仍然接收三个参数(与store上的dispatch一样)
// 
dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => { 
      //unifyObjectStyle 对额外传入的_options没有任何处理 只是确定一下位置
      const args = unifyObjectStyle(_type, _payload, _options)
      // options 值没有发生变化
      const { payload, options } = args
      let { type } = args
      // 在disptach的时候是否指定选择root(根)
      // 如果options设置为{root : true} 那么就会跳过下面
      if (!options || !options.root) {
        // 拼接真正的名字
        type = namespace + type
        if (process.env.NODE_ENV !== 'production' && !store._actions[type]) {
          console.error(`[vuex] unknown local action type: ${args.type}, global type: ${type}`)
          return
        }
      }
      // 调用(补全名字后)的action
      return store.dispatch(type, payload)
    },

这段代码我们可以看出来,local对象(也就是模块的context属性)中的dispacth会在未指定使用根模块名字时,会把dispatch调用的名字强行加上这个模块的全名,用这个dispatch调用的action都会变成你这个模块下的action

所以local中的dispatch与store中的disptach有什么不同

通俗的讲 我们想要调用A模块(有命名空间的某个action B)需要做的是

this.$store.dispatch('A模块的全名/B的名字'); 

在A模块的action中想要使用dispatch来做一些事情。

actions.js
export const ajaxGetUserName = ({dispatch})=>{
    // 这个时候用dispatch调用自己模块内的其余的action不需要加上全名
    dispatch('ajaxGetUserAge');
    // 想要变成和根模块一样的dispatch 需要加上一个options注明{root : true}
    // 这个时候dispatch就会变成全局的 不会主动帮你拼接全名了
}

export const ajaxGetUserAge = () => {
    // do something
}

同理local对象下的commit也是做了同样的事情,

这里就不多加解释了,相信聪明的你早就可以举一反三了。

两个方法说完了,下面该讲两个属性了

  1. getters与state

这两个属性就是我们的getters与state,但是这是我们local对象中,也就是局部模块下的getters与state。getters与state如何创建的 getters 首先判断全名是不是为空,为空就返回store对象的getters,有的话就创建局部getters。与其说是创建不如说是代理

如何创建局部的getters? 代理的方式

makeLocalGetters源码

function makeLocalGetters (store, namespace) {
  // 设计思想
  //其实我们并不需要创建一套getters,
  // 只要我们在local中通过getters来获取一些局部模块的值的时候,
  // 可以被代理到真正存放这些getters的地方。
  
  // 创建代理对象
  const gettersProxy = {}
  // 找到切割点
  const splitPos = namespace.length
  Object.keys(store.getters).forEach(type => {
    // skip if the target getter is not match this namespace
    // 得去getters里面找一下有没有这个namespace为前缀的getter。
    // 没有就找不到了
    if (type.slice(0, splitPos) !== namespace) return

    // extract local getter type
    // 拿到模块内注册的那个局部的getter名字
    // 全名是set/getName
    // localType就是getName
    const localType = type.slice(splitPos)

    // Add a port to the getters proxy.
    // Define as getter property because
    // we do not want to evaluate the getters in this time.
    // 完成代理任务,
    // 在查询局部名字是被代理到对应的store.getters中的(全名)getter
    Object.defineProperty(gettersProxy, localType, {
      get: () => store.getters[type],
      enumerable: true
    })
  })
  //返回代理对象
  return gettersProxy
}

创建局部的getters就是一个代理的过程,在使用模块内使用(没有加上命名空间的)getters的名字,会被代理到,store实例上那个真正的(全名的)getters。

state 这个相对来说就简单很多了

与代理类似,只是state只需要代理到state中对应那个模块的state,这个就比较简单了。

创建完毕

context是如何被创建的大家已经比较了解了。context的作用是什么? (local就是contenxt)之前说过注册mutation、action、getters都用到了local。用他们干什么?一一介绍

1. 注册mutation

我们注册的mutation在被commit调用时,使用的state是局部的state,当前模块内的state,所以不用特殊方式mutation无法更新父(祖先)模块和兄弟模块的内容。

2. 注册dispatch

dispatch是可以调用到模块内的mutation、disptach,也就是说它有更新模块内数据的能力,

但是只给了dispatch传入了store的getters与state(虽然有了这俩你想要什么放在vuex的数据都能得到),并没有给store的dispatch与mutation。

这就说名dispatch可以查看store中的所有数据,你放在vuex里面的数据我都可以看,但是你想改不使用特殊手段,不好意思只能改自己模块的。

3. 注册getters

getters并没有改变数据的能力,你愿意怎么操作数据都可以,模块内的数据,全模块的数据都可以给你,你愿意怎么计算都可以。

在注册中我们可以看到,vuex对这个改变数据的权限控制的很严格,但是查看数据控制的很松,改只能改自己模块的,查你愿意怎么看都OK。

总结

  1. context(也是local)是经过一个makeLocalContext的函数创建的,里面有局部的dispatch、commit方法和getters、state属性。
  2. 局部的方法属性都是只能访问局部模块内的,除非在使用时额外传入options({root:true})来解开局部模块的限制。
  3. 局部的getters是通过makeLocalGetters来实现的,主要思想是依靠代理的方式,把局部的名字的getter代理到store的getters中那个全名的getter。
  4. context 的作用可以帮助dispatch与commit控制更新数据的权限,帮助模块内getters拿到局部与全模块的数据。

这个章节结束,我们所有和模块有关的内容就已经完结了。 对于模块做一个小的总结。

模块的意义

  1. 模块与模块链接把Vuex初始化传入的内容,整理成一个方便处理的模块树(方便)
  2. 模块让action、mutation、getters、state都有了自己的全名(设置namespaced为true),起名字不再被约束,减少了命名冲突。
  3. 模块还给action、mutation、getters提供了局部上下文(context)让模块内的这些方法和属性,可以方便的修改模块内的数据以及获取全模块与模块内的数据。
  4. dispatch与commit也对模块进行了全力的支持(不支持不白做了吗),

所以模块为Vuex提供了很多方便,方便的去获取数据、修改数据。那么Vuex真正的数据仓库在哪里?数据都存储在哪里? 我们下一章见

我是一个应届生,最近和朋友们维护了一个公众号,内容是我们在从应届生过渡到开发这一路所踩过的坑,已经我们一步步学习的记录,如果感兴趣的朋友可以关注一下,一同加油~

个人公众号:咸鱼正翻身