Vuex源码分析

一、前言

我们都知道,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)
      })
    }
  }

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')

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

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值