Vuex(3.1.3)源码阅读——初始化

当我们在代码中通过 import Vuex from 'vuex' 的时候,实际上引用的是一个对象,它的定义在 src/index.js 中:

import { Store, install } from './store'
import { mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers } from './helpers'

export default {
  Store,//Store类,很多内置方法定义都在这个类里面
  install,//这个方法主要是保证值安装一次vuex并且在每个组件中的beforeCreate中初始化了vuex,初始化就是保证store添加到了Vue中
  version: '__VERSION__',
  mapState,//mapState方法
  mapMutations,//mapMutations方法
  mapGetters,//mapGetters方法
  mapActions,//mapActions方法
  createNamespacedHelpers//一个方法,通过改变mapState等的命名空间(作用域)
}

可以看到,Vuex就是一个普通的对象,每个属性上面都做了注释。那我们平常用vuex时候一般都是这么做的:

new Vuex.Store({
    actions,
    getters,
    state,
    mutations,
    modules
    // ...
  })
//然后就是把这个store实例化的对象后传给new Vue(..)

接下来就是看store初始化作了什么,store的定义在src/store.js

export class Store {
import applyMixin from './mixin'
import devtoolPlugin from './plugins/devtool'
import ModuleCollection from './module/module-collection'
import { forEachValue, isObject, isPromise, assert, partial } from './util'

let Vue // bind on install

export class Store {
  constructor (options = {}) {
    // Auto install if it is not done yet and `window` has `Vue`.
    // To allow users to avoid auto-installation in some cases,
    // this code should be placed here. See #731
    if (!Vue && typeof window !== 'undefined' && window.Vue) {
      install(window.Vue)
    }

    if (process.env.NODE_ENV !== 'production') {
      assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`)
      assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`)
      assert(this instanceof Store, `store must be called with the new operator.`)
    }

    const {
      plugins = [],
      strict = false
    } = options

    // store internal state
    this._committing = false
    this._actions = Object.create(null)
    this._actionSubscribers = []
    this._mutations = Object.create(null)
    this._wrappedGetters = Object.create(null)
    this._modules = new ModuleCollection(options)//把模块关系树整理好
    console.log('this._modules', this._modules)
    this._modulesNamespaceMap = Object.create(null)
    this._subscribers = []
    this._watcherVM = new Vue()
    this._makeLocalGettersCache = Object.create(null)

    // bind commit and dispatch to self
    const store = this
    const { dispatch, commit } = this
    this.dispatch = function boundDispatch (type, payload) {
      return dispatch.call(store, type, payload)
    }
    this.commit = function boundCommit (type, payload, options) {
      return commit.call(store, type, payload, options)
    }

    // strict mode
    this.strict = strict

    const state = this._modules.root.state

    // init root module.
    // this also recursively registers all sub-modules
    // and collects all module getters inside this._wrappedGetters
    installModule(this, state, [], this._modules.root)

    // initialize the store vm, which is responsible for the reactivity
    // (also registers _wrappedGetters as computed properties)
    resetStoreVM(this, state)

    // apply plugins
    plugins.forEach(plugin => plugin(this))

    const useDevtools = options.devtools !== undefined ? options.devtools : Vue.config.devtools
    if (useDevtools) {
      devtoolPlugin(this)
    }
  }

  get state () {
    return this._vm._data.$$state
  }

  set state (v) {
    if (process.env.NODE_ENV !== 'production') {
      assert(false, `use store.replaceState() to explicit replace store state.`)
    }
  }
}

先看构造器中,除了this._module,其他都是简单的初始化,我们先看这个new ModuleCollection(options)这个实例主要是整理了下模块关系树,定义在src/module/module-collection.js中。

import Module from './module'
import { assert, forEachValue } from '../util'

export default class ModuleCollection {
  constructor (rawRootModule) {
    // register root module (Vuex.Store options)
    this.register([], rawRootModule, false)
  }

  get (path) {
    return path.reduce((module, key) => {
      return module.getChild(key)//this._children[key]
    }, this.root)
  }

  getNamespace (path) {
    let module = this.root
    return path.reduce((namespace, key) => {
      module = module.getChild(key)
      return namespace + (module.namespaced ? key + '/' : '')
    }, '')
  }

  update (rawRootModule) {
    update([], this.root, rawRootModule)
  }

  register (path, rawModule, runtime = true) {
    if (process.env.NODE_ENV !== 'production') {
      assertRawModule(path, rawModule)
    }
    console.log('path22', path)
    const newModule = new Module(rawModule, runtime)
    if (path.length === 0) {//如果长度是0那么就将module赋给root,也就是确定了根模块
      this.root = newModule
    } else {
      const parent = this.get(path.slice(0, -1))//this._children[key]//也就是拿到了父模块
      parent.addChild(path[path.length - 1], newModule)//parent._children[path[path.length - 1]] = newModule//就是给父模块和子模块确定了关系
    }
    console.log('rawModule.modules', rawModule.modules)
    // register nested modules
    if (rawModule.modules) {//外部传过来的模块
      forEachValue(rawModule.modules, (rawChildModule, key) => {
        this.register(path.concat(key), rawChildModule, runtime)//每次递归的时候拿到是子模块的名,和子模块的体,比如a和{getters...},然后拿去注册
      })
    }
  }

  unregister (path) {
    const parent = this.get(path.slice(0, -1))
    const key = path[path.length - 1]
    if (!parent.getChild(key).runtime) return

    parent.removeChild(key)
  }

  isRegistered (path) {
    const parent = this.get(path.slice(0, -1))
    const key = path[path.length - 1]

    return parent.hasChild(key)
  }
}

可以看到这个ModelCollectiond的实例化就干了一件事

this.register([], rawRootModule, false)

再看register这个目录,结合上面代码中注释接着说:刚开始进去的时候,遇到什么跟生产环境有关的直接跳过,想想也知道和主要逻辑无关,接着毫无疑问,一个空数组,长度肯定为0,那么就作为了根模块了

    if (path.length === 0) {//如果长度是0那么就将module赋给root,也就是确定了根模块
      this.root = newModule
    }

接着else肯定进去了,就是看

    if (rawModule.modules) {//外部传过来的模块
      forEachValue(rawModule.modules, (rawChildModule, key) => {
        this.register(path.concat(key), rawChildModule, runtime)//每次递归的时候拿到是子模块的名,和子模块的体,比如a和{getters...},然后拿去注册
      })
    }

这断面代码的作用就是每次递归的时候拿到是子模块的名,和子模块的体,比如module a和它的内容{getters...},然后拿去注册,再走上面的流程一次,唯一不同的是这回可以进入上面的else分支了

   } else {
      const parent = this.get(path.slice(0, -1))//this._children[key]//也就是拿到了父模块
      parent.addChild(path[path.length - 1], newModule)//parent._children[path[path.length - 1]] = newModule//就是给父模块和子模块确定了关系
    }

get函数的定义:

  get (path) {
    return path.reduce((module, key) => {
      return module.getChild(key)//this._children[key]
    }, this.root)
  }

这2段代码的作用就是确定下父子关系。至此所有的父子关系经过一套递归就完成了。那些什么unregister 和isRegistered 就是递归删属性和判定对象中否存在,随便看看就懂了。

继续回到store类中:src/store.js
接下来要开始安装模块了:
先判断是否有命名空间,如果有命名空间就把模块添加到_modulesNamespaceMap中,如果不是根模块就将模块名添加为响应式对象。
然后通过makeLocalContext获取本地的上下文环境,
其中makeLocalGetters缓存了各个模块的getters到store的_makeLocalGettersCache上,
getNestedState 获得了模块最终的state状态,dispatch和commit都是根据命名空间和path进行定义,如果没有命名空间那么就直接指向了root模块
接下就是根据指定好的上下文(store,local等)注册mutations,actions和getters.
接着就是实例化Store._vm(通过resetStoreVM(store, state, hot)实现)
接着看resetStoreVM
resetStoreVM 首先遍历了 _wrappedGetters 获得每个 getter 的函数 fn 和 key,然后定义了 computed[key] = () => fn(store)。
_wrappedGetters 的初始化过程在registerWrapper中找,这里 fn(store) 相当于执行如下方法:

  store._wrappedGetters[type] = function wrappedGetter (store) {
    return rawGetter(
      local.state, // local state
      local.getters, // local getters
      store.state, // root state
      store.getters // root getters
    )
  }

接着就是给store添加._vm了

  store._vm = new Vue({
    data: {
      $$state: state
    },
    computed
  })

看这段代码:

  forEachValue(wrappedGetters, (fn, key) => {
    // use computed to leverage its lazy-caching mechanism
    // direct inline function use will lead to closure preserving oldVm.
    // using partial to return function with only arguments preserved in closure environment.
    computed[key] = partial(fn, store)
    Object.defineProperty(store.getters, key, {
      get: () => store._vm[key],
      enumerable: true // for local getters
    })
  })

也就是说拿着key去访问store.getters是遵循以下路径访问:

Store.getters[key] => store._vm[key] => computed[key] =>raw(local.state...store.state)

而访问store.state在class store中已经定义了

get state () {
    return this._vm._data.$$state
  }

也就是store.state => store._vm._data.$$state(就是实例化之前的那个state)

这样也就建立了state和getter的依赖关系,同时如果state发生了变化,那么getter由于computed的原因会去重新计算。

总结:

1. Vuex是一个普通的对象。

2. store的实例化是通过new ModelCollection去整理模块之间的关系,同时根据store和构建的上下文去注册mutation,action,getters,

3. 他们中间实现的链路就是

Store.getters[key] => store._vm[key] => computed[key] =>raw(local.state...store.state)=>this._vm._data.$$state

这样就达到了2种效果:

1. 通过store.state赋值给_vm.data达到store.state是响应式的。

2. 通过store.getter赋值给_vm.computed达到store.getters具有计算属性的能力。

返回目录

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,589评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,615评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,933评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,976评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,999评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,775评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,474评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,359评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,854评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,007评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,146评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,826评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,484评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,029评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,153评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,420评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,107评论 2 356

推荐阅读更多精彩内容