Skip to content

Latest commit

 

History

History
195 lines (162 loc) · 7.75 KB

3.3.Vuex核心功能解析.md

File metadata and controls

195 lines (162 loc) · 7.75 KB

认识Store构造函数

在使用vuex的时,我们实际上是实例化Store类,然后传入一个对象,这个对象包括我们定义好的state、getters、mutations、actions等,当然大点的项目还会有modules。

这一节我们就来看看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) {
      //通过浏览器script标签使用vuex时自动安装
      install(window.Vue)
    }

    if (process.env.NODE_ENV !== 'production') {
      //断言一些使用环境,必须使用vue.use(vuex),必须存在Promise,必须使用new操作符
      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) //这里会对模块module进行收集
    this._modulesNamespaceMap = Object.create(null)
    this._subscribers = []
    this._watcherVM = new Vue()

    // bind commit and dispatch to self
    //将dispatch和commit方法的this指针绑定到store上,防止被修改
    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
    //处理的核心,包括处理根module、action、mutation、getters和递归注册子module
    installModule(this, state, [], this._modules.root)

    // initialize the store vm, which is responsible for the reactivity
    // (also registers _wrappedGetters as computed properties)
    //也是重点,使用vue实例来保存state和getter
    resetStoreVM(this, state)

    // apply plugins
    //插件注册和使用,内置的有devtool和logger
    plugins.forEach(plugin => plugin(this))

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

我们来简要理一理这个构造过程,先是使用assert断言一下运行环境,然后初始化需要用到的变量,再就是模块安装installModule和组件初始化resetStoreVM,最后注册和使用插件。下面我们一一展开。

先是assert断言运行环境,这是一种很好的写法。assert函数在util.js文件中。这里断言必须先调用Vue.use(Vuex)。必须提供Promise,应该是为了让Vuex的体积更小,让开发者自行提供Promise的polyfill,一般我们可以使用babel-runtime或者babel-polyfill引入。最后断言必须使用new操作符调用Store函数。

  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.`)

然后是一些内部变量的初始化,下面我们先对这些变量的作用进行下说明便于后面的理解。

    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) //这里会对模块module进行收集
    this._modulesNamespaceMap = Object.create(null)
    this._subscribers = []
    this._watcherVM = new Vue()

_committing提交状态的标志,在_withCommit中,当使用mutation时,会先赋值为true,再执行mutation,修改state后再赋值为false, 在这个过程中,会用watch监听state的变化时是否_committing为true,从而保证只能通过mutation来修改state

_actions用于保存所有action,里面会先包装一次 _actionSubscribers用于保存订阅action的回调 _mutations用于保存所有的mutation,里面会先包装一次 _wrappedGetters用于保存包装后的getter _modules用于保存一棵module树 _modulesNamespaceMap用于保存namespaced的模块 _subscribers用于存储所有对 mutation 变化的订阅者 _watcherVM Vue对象的实例,主要是利用 Vue 实例方法 $watch 来观测变化的

这里提一下this._modules = new ModuleCollection(options)对模块module进行收集的逻辑。实际上是调用了module目录下的ModuleCollection类和Module类。

//module目录下的moduleCollection.js
export default class ModuleCollection {
  constructor (rawRootModule) {
    // register root module (Vuex.Store options)
    this.register([], rawRootModule, false)
  }
  /** 省略 **/
}

//module目录下的module.js
export default class Module {
  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
    const rawState = rawModule.state

    // Store the origin module's state
    this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}
  }
  /** 省略 **/
}

这里调用ModuleCollection构造函数,首先进行根module的注册,然后递归遍历所有的module,子module 添加到父module的_children属性上,最终形成一棵树。

再就是模块安装installModule函数,这里是module的处理核心,包括处理根module、命名空间、action、mutation、getters和递归注册子module。

function installModule (store, rootState, path, module, hot) {
  const isRoot = !path.length
  const namespace = store._modules.getNamespace(path)

  // register in namespace map
  //保存namespaced模块
  if (module.namespaced) {
    store._modulesNamespaceMap[namespace] = module
  }

  // set state
  //非根组件设置state,根据path获取父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)
    })
  }

  //设置module的上下文,从而保证mutation和action的第一个参数能拿到对应的state getter等
  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)
  })
}