vuex源码浅析

前言

当前版本是3.1.0,这个版本主要引入了mutations的异步前后的两个钩子
debug的项目是官方例子里的shopping-cart,这个项目的有两个modules,可以看的比较直观。

个人理解

vuex就是维护一个Store对象的状态树。而他下一级如果直接就是state的话,那么当数据量大时就会很影响性能,通过分治的思想,引入了modules。

源码

constructor主要是进行一些初始化的操作

if (!Vue && typeof window !== 'undefined' && window.Vue) {
      install(window.Vue)
    }

这里主要是判断是不是全局引入的vue及是不是浏览器环境,全局引入就自动注册进vue里,就是用npm引入的那种vue.use(vuex),install主要实现了applyMixin方法

export default function (Vue) {
  const version = Number(Vue.version.split('.')[0])

  if (version >= 2) {
    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
    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
    }
  }
}

这里显示判断vue的版本,如果是vue2就调用mixin,把vuexInit插入到beforeCreate钩子之前,vue1就不用说了….

回到constructor

  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)
    this._modulesNamespaceMap = Object.create(null)
    this._subscribers = []
    this._watcherVM = new Vue()

这里主要是初始化store内部的一些对象,关键是这个ModuleCollection,他把modules初始化之后,之后mutations改变state直接照这个_modules就好了
这里我们直接看代码可能会很难看懂,可以直接运行官方例子进行加深理解。
这里的options是

new Vuex.Store({
  modules: {
    cart,
    products
  },
  strict: debug,
  plugins: debug ? [createLogger()] : []
})

我们进入ModuleCollection,

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

register (path, rawModule, runtime = true) {
    if (process.env.NODE_ENV !== 'production') {
      assertRawModule(path, rawModule)
    }

    const newModule = new Module(rawModule, runtime)
    if (path.length === 0) {
      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) {
      forEachValue(rawModule.modules, (rawChildModule, key) => {
        this.register(path.concat(key), rawChildModule, runtime)
      })
    }
  }

这里的逻辑是给options里的每一个modules new一个modules对象,放在_children下,options放在_rawModule下,此时的this。_modules

ModuleCollection {root: Module}
root: Module
runtime: false
state: {}
_children:
cart: Module {runtime: false, _children: {…}, _rawModule: {…}, state: {…}}
products: Module {runtime: false, _children: {…}, _rawModule: {…}, state: {…}}
_rawModule:
modules: {cart: {…}, products: {…}}
plugins: [ƒ]
strict: true
__proto__: Object
namespaced: (...)
__proto__: Object
__proto__: Object

回到constructor

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

这里是为了强制this指向的是store
先看commit,在vuex里,改变state的唯一方法是提交commit来触发_mutations,actions最后也是通过_mutations来改变的

 commit (_type, _payload, _options) {
    // check object-style commit
    const {
      type,
      payload,
      options
    } = unifyObjectStyle(_type, _payload, _options)

    const mutation = { type, payload }
    const entry = this._mutations[type]
    if (!entry) {
      if (process.env.NODE_ENV !== 'production') {
        console.error(`[vuex] unknown mutation type: ${type}`)
      }
      return
    }
    this._withCommit(() => {
      entry.forEach(function commitIterator (handler) {
        handler(payload)
      })
    })
    this._subscribers.forEach(sub => sub(mutation, this.state))

    if (
      process.env.NODE_ENV !== 'production' &&
      options && options.silent
    ) {
      console.warn(
        `[vuex] mutation type: ${type}. Silent option has been removed. ` +
        'Use the filter functionality in the vue-devtools'
      )
    }
  }

先通过_type来找到_mutations,然后改变state之后,触发_subscribers,通知订阅者,实现数据的双向绑定。

dispatch

dispatch (_type, _payload) {
    // check object-style dispatch
    const {
      type,
      payload
    } = unifyObjectStyle(_type, _payload)

    const action = { type, payload }
    const entry = this._actions[type]
    if (!entry) {
      if (process.env.NODE_ENV !== 'production') {
        console.error(`[vuex] unknown action type: ${type}`)
      }
      return
    }

    try {
      this._actionSubscribers
        .filter(sub => sub.before)
        .forEach(sub => sub.before(action, this.state))
    } catch (e) {
      if (process.env.NODE_ENV !== 'production') {
        console.warn(`[vuex] error in before action subscribers: `)
        console.error(e)
      }
    }

    const result = entry.length > 1
      ? Promise.all(entry.map(handler => handler(payload)))
      : entry[0](payload)

    return result.then(res => {
      try {
        this._actionSubscribers
          .filter(sub => sub.after)
          .forEach(sub => sub.after(action, this.state))
      } catch (e) {
        if (process.env.NODE_ENV !== 'production') {
          console.warn(`[vuex] error in after action subscribers: `)
          console.error(e)
        }
      }
      return res
    })
  }

这里由于_actions是异步的,所以会判断他是不是Promise,不是就new 一个Promise给他,这里需要的是_actionSubscribers运行了两次,这是这个版本加上的两个action的勾子函数。

回到constructor

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

这是注册_mutations, _actions等数据的

resetStoreVM(this, state)

这是注册订阅者的

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

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

这是装载插件,例如vue-devtools

    原文作者:fuqihan
    原文地址: https://segmentfault.com/a/1190000018840001
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞