Vuex源码分析

422 阅读1分钟

Vuex安装

目录结构

import Vue from 'vue'
import Vuex from 'vuex'
//注册vuex
Vue.use(Vuex)
//安装时执行 install方法
export function install (_Vue) {
  if (Vue && _Vue === Vue) {
    if (process.env.NODE_ENV !== 'production') {
      console.error(
        '[vuex] already installed. Vue.use(Vuex) should be called only once.'
      )
    }
    return
  }
  Vue = _Vue
  applyMixin(Vue)
}
export default function (Vue) {
  const version = Number(Vue.version.split('.')[0])
//版本判断
  if (version >= 2) {
    // vuexInit  
    Vue.mixin({ beforeCreate: vuexInit })
  } else {
    // 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 () {
    const options = this.$options
    // store injection  每个组件都有store的实例
    if (options.store) {
      this.$store = typeof options.store === 'function'
        ? options.store()
        : options.store
    } else if (options.parent && options.parent.$store) {
      this.$store = options.parent.$store
    }
  }
}

vuex注册时执行install方法,在 install 方法中, 调用了applyMixin 方法,applyMixin方法主要确保每个组件都有store的实例,都可以使用store实例

Store实例化

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
    //不通过npm开发代码
    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.`)
    //当前环境是否支持promise
      assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`)
      //判断this是vuex的一个实例
      assert(this instanceof Store, `store must be called with the new operator.`)
    }

    const {
      //vue支持的插件
      plugins = [],
      strict = false
    } = options

    // store internal state
    //实例store上的私有属性
    this._committing = false
    this._actions = Object.create(null)
    this._actionSubscribers = []
    this._mutations = Object.create(null)
    this._wrappedGetters = Object.create(null)
    //初始化modules的过程
    this._modules = new ModuleCollection(options)
    this._modulesNamespaceMap = Object.create(null)
    this._subscribers = []
    this._watcherVM = new Vue()

    // bind commit and dispatch to self
    const store = this
    const { dispatch, commit } = this
    this.dispatch = function boundDispatch (type, payload) {
      //上下文为store
      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
    //actions,mutation 。。做赋值
    installModule(this, state, [], this._modules.root)

    // initialize the store vm, which is responsible for the reactivity
    // (also registers _wrappedGetters as computed properties)
    //getter,state建立依赖关系  响应式
    resetStoreVM(this, state)

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

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

store实例化过程主要定义私有属性的初始化,确保 dispatch/commit 方法中的 this 对象正确指向 store

Modules初始化

install把仓库拆分成小仓库,注册mutation,action,getter递归建立树形数据结构, installModule 接收5个参数: store、rootState、path、module、hot. store 表示当前 Store 实例, rootState 表示根 state, path 表示当前嵌套模块的路径数组, module 表示当前安装的模块

function installModule (store, rootState, path, module, hot) {
  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(() => {
      Vue.set(parentState, moduleName, module.state)
    })
  }
//makeLocalContext  设置上下文
  const local = module.context = makeLocalContext(store, namespace, path)
//做mutation 注册
  module.forEachMutation((mutation, key) => {
    const namespacedType = namespace + key
    //创建mutation数组 
    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.forEachChild((child, key) => {
    installModule(store, rootState, path.concat(key), child, hot)
  })
}

语法糖

 //支持数组或对象   调用map方法  最后都会变成key value数组
function normalizeMap (map) {
  return Array.isArray(map)
    ? map.map(key => ({ key, val: key }))
    : Object.keys(map).map(key => ({ key, val: map[key] }))
}

 //执行
function normalizeNamespace (fn) {
  //两个参数 模块会传namespace  对两个参数,一个参数的做相应处理
  return (namespace, map) => {
    if (typeof namespace !== 'string') {
      map = namespace
      namespace = ''
      //不传第一个参数,自动拼接斜线
    } else if (namespace.charAt(namespace.length - 1) !== '/') {
      namespace += '/'
    }
    return fn(namespace, map)
  }
}
export const mapActions = normalizeNamespace((namespace, actions) => {
  const res = {}
    //遍历key value
  normalizeMap(actions).forEach(({ key, val }) => {
      //每一个值都是函数
    res[key] = function mappedAction (...args) {
      // get dispatch function from store
      
      let dispatch = this.$store.dispatch
      if (namespace) {
      //返回相应模块
        const module = getModuleByNamespace(this.$store, 'mapActions', namespace)
        if (!module) {
          return
        }
        dispatch = module.context.dispatch
      }
      return typeof val === 'function'
        ? val.apply(this, [dispatch].concat(args))
        : dispatch.apply(this.$store, [val].concat(args))
    }
  })
  return res
})

该方法会将 store 中的 dispatch 方法映射到组件的 methods 中

动态注册modules

//动态注入新的modules  
  registerModule (path, rawModule, options = {}) {
    if (typeof path === 'string') path = [path]
//path不能为0 只能做扩展 
    if (process.env.NODE_ENV !== 'production') {
      assert(Array.isArray(path), `module path must be a string or an Array.`)
      assert(path.length > 0, 'cannot register the root module by using registerModule.')
    }
//重新对modules做扩展
    this._modules.register(path, rawModule)
    //把新的模块的mutation,action 扩展进去
    installModule(this, this.state, path, this._modules.get(path), options.preserveState)
    // reset store to update getters...
    resetStoreVM(this, this.state)
  }
//注销
  unregisterModule (path) {
    if (typeof path === 'string') path = [path]

    if (process.env.NODE_ENV !== 'production') {
      assert(Array.isArray(path), `module path must be a string or an Array.`)
    }

    this._modules.unregister(path)
    this._withCommit(() => {
      const parentState = getNestedState(this.state, path.slice(0, -1))
      Vue.delete(parentState, path[path.length - 1])
    })
    //做新modules 注销 ,重新整理action ,mutation ,,,
    resetStore(this)
  }