Vuex源码分析

2019-12-8    seo达人

一、前言

我们都知道,vue组件中通信是用props进行父子通信,或者用provide和inject注入的方法,后者类似与redux的porvider,父组件使用,包含在里面的子组件都可以使用,provide/inject用法看我的博客(provide/inject用法),provide/indect官方文档,不过provide/indect一般用的不多,都是用前者,但是props有一个问题,父传子没问题,但是子后面还有子,子后面还有子,子子孙孙无穷尽也,所以这就要一层层的传下去,太麻烦了,所以vuex就派上用场了,vuex作为一个很轻量的状态管理器,有着简单易用的的API接口,在任意组件里面都能使用,获取全局状态,统一获取改变,今天,就看一下源码怎么实现的。



二、准备

1)先去github上将vuex源码下载下来。

2)打开项目,找到examples目录,在终端,cd进入examples,执行npm i,安装完成之后,执行node server.js



3)执行上述之后,会得到下方的结果,表示编译完成,打开浏览器 输入 localhost:8080



4) 得到页面,点击counter



5)最终,我们就从这里出发调试吧。





三、调试

找到counter目录下的store.js 写一个debugger,浏览器打开F12,刷新页面,调试开始。



1.Vue.use()

先进入Vue.use(Vuex),这是Vue使用插件时的用法,官方文档也说了,Vue.use,会调用插件的install方法,所以如果你要写插件的话,你就要暴露一个install方法,详情请看vue官方文档之use的用法



即use会调用下方的方法(debugger会进入)



function initUse (Vue) {

  Vue.use = function (plugin) {

    var installedPlugins = (this._installedPlugins || (this._installedPlugins = []));

    if (installedPlugins.indexOf(plugin) > -1) {

      return this

    }



    // additional parameters

    var args = toArray(arguments, 1);

    args.unshift(this);

    if (typeof plugin.install === 'function') { 

    // 如果插件的install是一个function,调用install,将 this指向插件,并将Vue作为第一个参数传入

    // 所以调用vuex吧this指向vuex,并吧vue当参数传入

      plugin.install.apply(plugin, args);

    } else if (typeof plugin === 'function') {

      plugin.apply(null, args);

    }

    installedPlugins.push(plugin);

    return this

  };

}





1.1 vuex的install方法

在源码目录的src目录下的store.js文件里最下方有个install函数,会调用它



debugger进入后



export function install (_Vue) { // _Vue是上面说的Vue作为第一个参数 ,指针this指向Vuex

  if (Vue && _Vue === Vue) {

   // 如果你在开发环节,你使用了两次Vue.use(Vuex) 就会报下方的错误,提醒你vue只能被use一次,可以自行试试

    if (process.env.NODE_ENV !== 'production') {

      console.error(

        '[vuex] already installed. Vue.use(Vuex) should be called only once.'

      )

    }

    return

  }

  Vue = _Vue

  applyMixin(Vue) // 调用applyMixin方法

}



1.2 vuex的applyMixin方法

applyMixin方法在mixin.js文件里 同样在src目录下



export default function (Vue) {

  const version = Number(Vue.version.split('.')[0]) // 获取vue的版本



  if (version >= 2) { // vue的版本是2.xx以上 执行vue的mixin混入,该函数不懂用法请查看官方文档,

  // mixin合并混入操作,将vuexInit 方法混入到beforeCreate生命周期,意思就是当执行beforeCreate周期的时候

  // 会执行vuexInit 方法

    Vue.mixin({ beforeCreate: vuexInit })

  } else { // 1.xxx版本太老了 现在基本不用,暂不讲解,可以自行了解

    // override init and inject vuex init procedure

    // for 1.x backwards compatibility.

    const _init = Vue.prototype._init

    Vue.prototype._init = function (options = {}) {

      options.init = options.init

        ? [vuexInit].concat(options.init)

        : vuexInit

      _init.call(this, options)

    }

  }



  /*

   
Vuex init hook, injected into each instances init hooks list.

   /



  function vuexInit () { 

  // 因为该方法是在beforeCreate下执行,而beforeCreate的this指向为Vue 所以this === Vue

  // 获得vue里面的option配置,这里涉及到vue的源码,以后再讲vue ,

  //所以这就是我们为什么要在new Vue的时候,传递一个store进去的原因,

  //因为只有传进去,才能在options中获取到store

  /


  var vm = new Vue({

el: "#app",

data() {return{}},

store

})

*/

    const options = this.$options

    // store injection

    if (options.store) { 

    // 如果options对象中store有,代表是root ,如果options.store是函数,执行调用options.store()

    // 如果是对象直接options.store 赋值给this.$stroe

  // 这也就是我们为什么能够在Vue项目中直接this.$store.disptach('xxx')的原因,从这里获取

      this.$store = typeof options.store === 'function'

        ? options.store()

        : options.store

    } else if (options.parent && options.parent.$store) { 

    // 如果options.store为空,则判断options.parent.$store有没有 从父元素判断有没有store,

    //从而保证子元素父元素公用一个store实例

      this.$store = options.parent.$store

    }

  }

}





1.3 Vue.use(Vuex)总结

至此,Vue.use(Vuex)全部分析完成,总结,就是Vue.use调用Vuex的install的方法,然后install使用mixin混入beforecreate生命周期中混入一个函数,当执行生命周期beforecreate的时候回执行vuexInit 。你可以慢慢调试,所以要好好利用下方的调试按钮,第二个按钮执行下一步,第三个进入方法,两个配合使用。





2.new Vuex.Store

Vue.use(Vuex)已经结束,再回到counter目录下的store.js文件



export default new Vuex.Store({

  state,

  getters,

  actions,

  mutations

})





debugger进入,Vuex.Store方法在src目录下的store.js文件下



export class Store {

  constructor (options = {}) {

  // 这里的options是在counter定义的 state,getters,actions,mutations当做参数传进来

    // 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) {

    // 挂载在window上的自动安装,也就是通过script标签引入时不需要手动调用Vue.use(Vuex)

      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

    //下方是在Vuex的this上挂载一些对象,这里暂且不要知道他们要来干什么

    // 因为是源码分析,不要所有的代码都清除,第一次源码分析,你就只当他们是挂载对象,往下看

    this._committing = false

    this._actions = Object.create(null)

    this._actionSubscribers = []

    this._mutations = Object.create(null)

    this._wrappedGetters = Object.create(null)

    // ModuleCollection代表模块收集,形成模块树

    this._modules = new ModuleCollection(options) //碰到第一个不是定义空对象,debugger进去,分析在下面

    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) { // 绑定dispatch的指针为store

      return dispatch.call(store, type, payload)

    }

    this.commit = function boundCommit (type, payload, options) { // 绑定commit的指针为store

      return commit.call(store, type, payload, options)

    }



    // strict mode

    this.strict = strict



    const state = this._modules.root.state // 获取到用户定义的state



    // init root module.

    // this also recursively registers all sub-modules

    // and collects all module getters inside this._wrappedGetters

    // 初始化root模块 注册getters,actions,mutations 子模块

    installModule(this, state, [], this._modules.root)



    // initialize the store vm, which is responsible for the reactivity

    // (also registers _wrappedGetters as computed properties)

    // 初始化vm 用来监听state getter

    resetStoreVM(this, state)



    // apply plugins

    // 插件的注册

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

// 下方的是开发工具方面的 暂不提

    const useDevtools = options.devtools !== undefined ? options.devtools : Vue.config.devtools

    if (useDevtools) {

      devtoolPlugin(this)

    }

  }

  }



2.1 new ModuleCollection

ModuleCollection函数在src目录下的module目录下的module-collection.js文件下



export default class ModuleCollection {

  constructor (rawRootModule) { // rawRootModule === options

    // register root module (Vuex.Store options)

    this.register([], rawRootModule, false)

  }

}



2.1.1 register()



 register (path, rawModule, runtime = true) {

 // register([],options,false)

    if (process.env.NODE_ENV !== 'production') {

      assertRawModule(path, rawModule) // 开发环境断言,暂忽略

    }



    const newModule = new Module(rawModule, runtime)

    if (path.length === 0) { // path长度为0,为根节点,将newModule 赋值为root

      this.root = newModule

    } else {

      const parent = this.get(path.slice(0, -1))

      parent.addChild(path[path.length - 1], newModule)

    }



    // register nested modules

    if (rawModule.modules) { // 如果存在子模块,递归调用register,形成一棵树

      forEachValue(rawModule.modules, (rawChildModule, key) => {

        this.register(path.concat(key), rawChildModule, runtime)

      })

    }

  }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

2.1.2 new Module()

Module函数在同目录下的modele.js文件下



export default class Module {

// new Module(options,false)

  constructor (rawModule, runtime) {

    this.runtime = runtime

    // Store some children item

    this._children = Object.create(null)

    // Store the origin module object which passed by programmer

    this._rawModule = rawModule // 将options放到Module上 用_raModele上

    const rawState = rawModule.state // 将你定义的state取出



    // Store the origin module's state

    // 如果你定义的state为函数,调用函数,为对象,则取对象 赋值为module上的state上

    this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}

  }

}



所以Module的this内容为如下:



2.1.3 installModule



function installModule (store, rootState, path, module, hot) {

// installModule(Vuex,state,[],module) module是前面 new ModuleCollection产生的对象

  const isRoot = !path.length

  const namespace = store._modules.getNamespace(path)



  // register in namespace map

  if (module.namespaced) {

    if (store._modulesNamespaceMap[namespace] && process.env.NODE_ENV !== 'production') {

      console.error([vuex] duplicate namespace ${namespace} for the namespaced module ${path.join('/')})

    }

    store._modulesNamespaceMap[namespace] = module

  }



  // set state

  if (!isRoot && !hot) {

    const parentState = getNestedState(rootState, path.slice(0, -1))

    const moduleName = path[path.length - 1]

    store._withCommit(() => {

      if (process.env.NODE_ENV !== 'production') {

        if (moduleName in parentState) {

          console.warn(

            [vuex] state field "${moduleName}" was overridden by a module with the same name at "${path.join('.')}"

          )

        }

      }

      Vue.set(parentState, moduleName, module.state)

    })

  }

// 设置当前上下文

  const local = module.context = makeLocalContext(store, namespace, path)

// 注册mutation

  module.forEachMutation((mutation, key) => {

    const namespacedType = namespace + key

    registerMutation(store, namespacedType, mutation, local)

  })

// 注册action

  module.forEachAction((action, key) => {

    const type = action.root ? key : namespace + key

    const handler = action.handler || action

    registerAction(store, type, handler, local)

  })

// 注册getter

  module.forEachGetter((getter, key) => {

    const namespacedType = namespace + key

    registerGetter(store, namespacedType, getter, local)

  })

// 逐一注册子module

  module.forEachChild((child, key) => {

    installModule(store, rootState, path.concat(key), child, hot)

  })

}



2.1.4 makeLocalContext



// 设置module的上下文,绑定对应的dispatch、commit、getters、state

function makeLocalContext (store, namespace, path) {

  const noNamespace = namespace === ''



  const local = {

   //noNamespace 为true 使用原先的 至于后面的 不知道干啥用的 后面继续研究

    dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => {

      const args = unifyObjectStyle(_type, _payload, _options)

      const { payload, options } = args

      let { type } = args



      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

        }

      }



      return store.dispatch(type, payload)

    },



    commit: noNamespace ? store.commit : (_type, _payload, _options) => {

    //noNamespace 为true 使用原先的

      const args = unifyObjectStyle(_type, _payload, _options)

      const { payload, options } = args

      let { type } = args



      if (!options || !options.root) {

        type = namespace + type

        if (process.env.NODE_ENV !== 'production' && !store._mutations[type]) {

          console.error([vuex] unknown local mutation type: ${args.type}, global type: ${type})

          return

        }

      }



      store.commit(type, payload, options)

    }

  }



  // getters and state object must be gotten lazily

  // because they will be changed by vm update

  Object.defineProperties(local, {

    getters: {

      get: noNamespace

        ? () => store.getters

        : () => makeLocalGetters(store, namespace)

    },

    state: {

      get: () => getNestedState(store.state, path)

    }

  })



  return local

}



function getNestedState (state, path) {

  return path.reduce((state, key) => state[key], state)

}

2.1.5 registerMutation

mutation的注册,会调用下方方法,将wrappedMutationHandler函数放入到entry中



function registerMutation(store, type, handler, local) {

 // entry和store._mutations[type] 指向同一个地址

  var entry = store._mutations[type] || (store._mutations[type] = []);

  entry.push(function wrappedMutationHandler(payload) {

    handler.call(store, local.state, payload);

  });

}





2.1.6 forEachAction

action的注册,会调用下方方法,将wrappedActionHandler函数放入到entry中



function registerAction(store, type, handler, local) {

  var entry = store._actions[type] || (store._actions[type] = []);

   // entry和store._actions[type]指向同一个地址

  entry.push(function wrappedActionHandler(payload) {

    var res = handler.call(store, {

      dispatch: local.dispatch,

      commit: local.commit,

      getters: local.getters,

      state: local.state,

      rootGetters: store.getters,

      rootState: store.state

    }, payload);

    if (!(0, _util.isPromise)(res)) {

      res = Promise.resolve(res);

    }

    if (store._devtoolHook) {

      return res.catch(function (err) {

        store._devtoolHook.emit('vuex:error', err);

        throw err;

      });

    } else {

      return res;

    }

  });

}



因为entry和上面的_action[type],_mutations[type] 指向同一个地址,所以:



2.1.7 forEachGetter

getter的注册,会调用下方方法



function registerGetter(store, type, rawGetter, local) {

  if (store._wrappedGetters[type]) {

    if (true) {

      console.error('[vuex] duplicate getter key: ' + type);

    }

    return;

  }

  store._wrappedGetters[type] = function wrappedGetter(store) {

    return rawGetter(local.state, // local state

    local.getters, // local getters

    store.state, // root state

    store.getters // root getters

    );

  };

}







2.1.8 resetStoreVM



function resetStoreVM (store, state, hot) {

  const oldVm = store._vm //将_vm用变量保存



  // bind store public getters

  store.getters = {}

  // reset local getters cache

  store._makeLocalGettersCache = Object.create(null)

  const wrappedGetters = store._wrappedGetters // 获取installModule方法完成的_wrappedGetters内容

  const 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]中的值,即可以通过 this.$store.getters.xxx访问值

      get: () => store._vm[key],

      enumerable: true // for local getters

    })

  })



  // use a Vue instance to store the state tree

  // suppress warnings just in case the user has added

  // some funky global mixins

  const silent = Vue.config.silent

  // silent设置为true,取消所有日志警告等

  Vue.config.silent = true

  store._vm = new Vue({ // 将state,getter的值进行监听,从这里就可以看出getter其实就是采用的vue的computed

    data: {

      $$state: state

    },

    computed

  })

  // 恢复原先配置

  Vue.config.silent = silent



  // enable strict mode for new vm

  if (store.strict) {

    enableStrictMode(store)

  }

// 若存在旧的实例,解除对state的引用,等dom更新后把旧的vue实例销毁

  if (oldVm) {

    if (hot) {

      // dispatch changes in all subscribed watchers

      // to force getter re-evaluation for hot reloading.

      store._withCommit(() => {

        oldVm._data.$$state = null

      })

    }

    Vue.nextTick(() => oldVm.$destroy())

  }

}



看到这里,你应该对vuex有初步的了解



 // 这也是我们为什么能用访问到state,getter的原因

 //this.store.dispatch('xxx') ,this.$store.dispatch('xxx')

1

2

相信你也有点乱,其实上面很多我没讲到的不是我不想讲,是具体我也不知道干啥的,看源码学习呢,看主要就行,后面理解多了,其他的就慢慢都会,你不可能刚开始看,就每一行,他写的每一句的用途都知道是干什么的,只能先看主要方法,在慢慢琢磨,一步步来吧,别急,现在我来画一个流程图,更好的去理解吧。

2.1.9 流程图





3.连贯

import Vue from 'vue'

import Counter from './Counter.vue'

import store from './store'



new Vue({

  el: '#app',

  store,

  render: h => h(Counter)

})



当运行new Vue的时候,传入了store,前面minix beforecreate,执行到beforecreate钩子时,会调用vueInit函数,就可以在this.$store取到store对象了,因为options.store有值了 ,不为空,这样就连贯到了,所以这就是为什么可以用this.$store取值。


分享本文至:

日历

链接

个人资料

蓝蓝设计的小编 http://www.lanlanwork.com

存档