vuex源码解读

325 阅读5分钟

前言:设计思想

  1. 采用单例设计模式,全局维护一个对象,对象中所有属性均是响应式
  2. 只能通过commit改变状态,单向数据流模式
  3. 模块化;

一、install

vuex中的install函数提供了一个全局混入对象,在beforeCreate生命周期里将store对象赋值给this.$store,保证每个组件都可以通过this.$store访问到store对象

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
    }
}
1. 如何实现state的响应式

vuex将配置中的state传入Vue实例中,通过Vue来实现state的响应;

store.vm = new Vue({
    data:{
      $$state:state
    },
    computed
  })

store对象对state属性设置存值函数和取值函数,取值函数直接就取上述vue实例上的?state,当通过存值函数直接修改state的值时抛出错误;

get state () {
    return this._vm._data.$$state
  }
set state (v) {
    if (process.env.NODE_ENV !== 'production') {
      ...// 抛出错误
    }
}
2.如何实现commit的单向数据流模式

通过定义一个全局变量_committing = false,当使用commit修改状态时,_committing的值变为true;

 commit(_type,_payload,_options){
    //...
    // 找到要执行的mutation,在_withCommit中执行
    this._withCommit(fn)
}

_withCommit (fn) {
    const committing = this._committing
    this._committing = true
    // 在执行mutations中的mutation时,committing的状态为true,此时strict模式下是不会报错的,
    // 如果直接修改state中的值,committing就是false,这是在严格模式下就会报错
    fn()
    this._committing = committing
      }

同时在严格模式下设置监听state的改变,当state改变时,如果_committing的值为true,则是通过commit改变的,否则就会抛出错误;

store._vm.$watch(function () { return this._data.$$state }, () => {
    if (process.env.NODE_ENV !== 'production') {
    // 通过判断_committing的值来确定是否是通过commit修改状态
     assert(store._committing, `do not mutate vuex store state outside mutation handlers.`)
    }
  }, { deep: true, sync: true })

同时也可以看出为什么mutations中的处理函数为什么一定要是同步函数!因为如果是异步函数,state的状态改变发生在回调函数中,那么回调函数什么时候被触发是不可追踪的,也就无法记录快照了;

二、vuex初始化流程 - Store对象的构建

1.ModuleCollection

Vuex允许将store分割成模块,每个模块拥有自己的statemutationactiongetter,甚至是嵌套子模块,从上自下进行同样方式的分割;

ModuleCollection类的作用递归注册所有module;ModuleCollection类提供了两个方法getNamespace(path)get(path),这里的path参数是一个数组,记录这当前module到根module的路径(即模块名);

// 根据path取到相应的module
get(path){
    return path.reduce((module,next) => {
      return module.getChild(next)
    },this.root)
}

//根据path获取当前模块的注册名称
// 用于注册命名空间下的actions、mutations和getters
getNamespace(path){
    let module = this.root
    return path.reduce((namespace,next) => {
      module = module.modules[next]
      return namespace + (module.namespaced ? next + '/' : '')
    },'')
}
2.installModules

通过ModuleCollection得到实例化对象this.modulethis._modules.root即根module;

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

installModule函数将我们在配置项中提供的state、mutations、actions、getters注册到store对象中,使得我们可以通过commitdispatch等方式触发;

/**
* @param store 当前store对象
* @param rootState 根state对象
* @param path 存储当前注册模块的路径数组
* @param module 当前注册模块
* @hot 是否为热更新
*/
function installModule (store, rootState, path, module, hot) {
  const isRoot = !path. // 判断是否为根模块
  const namespace = store._modules.getNamespace(path) // 获取当前模块的命名空间
  
  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 // 命名空间路径注册
  }

  // 对当前模块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添加state
      Vue.set(parentState, moduleName, module.state)
    })
  }
   // 获取模块内部状态 context 对象
  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.forEachChild((child, key) => {
    installModule(store, rootState, path.concat(key), child, hot)
  })
}

3. registerMutation
/**
* @param store Store对象
* @param type 注册路径
* @param handler mutations中定义的处理函数
* @param local 当前命名空间内的context
*/
function registerMutation (store, type, handler, local) {
  const entry = store._mutations[type] || (store._mutations[type] = [])
  // 
  entry.push(function wrappedMutationHandler (payload) {
  // handler内部this指针指向Store对象,第一个参数是当前模块下的state状态,
  // mutation中注册的处理函数,总是接收state作为第一个参数,如果定义在模块中,则为模块的局部状态,payload作为第二个参数;
    handler.call(store, local.state, payload)
  })
}
4.registerAction
/**
* @param store Store对象
* @param type 注册路径
* @param handler actionss中定义的处理函数
* @param local 当前命名空间内的context
*/
function registerAction (store, type, handler, local) {
  const entry = store._actions[type] || (store._actions[type] = [])
  entry.push(function wrappedActionHandler (payload) {  
   //模块内部的action中注册的处理函数,接收context作为第一个参数,context在模块内部代表当前模块内的局部状态对象,包含state(局部状态),getters,rootGetters,rootState(全局状态),commit(局部处理函数,只能触发模块内部的commit),dispatch(局部处理函数)
    let res = handler.call(store, {
      dispatch: local.dispatch,
      commit: local.commit,
      getters: local.getters,
      state: local.state,
      rootGetters: store.getters,
      rootState: store.state
    }, payload)
    // 不是Promise对象就使用Promise.resolve(res)包裹一下,是dispatch可以链式处理;
    if (!isPromise(res)) {
      res = Promise.resolve(res)
    }
    if (store._devtoolHook) {
      return res.catch(err => {
        store._devtoolHook.emit('vuex:error', err)
        throw err
      })
    } else {
      return res
    }
  })
}
5.registerGetter
/**
* @param store Store对象
* @param type 注册路径
* @param rawGetter getters中定义的处理函数
* @param local 当前命名空间内的context
*/
function registerGetter (store, type, rawGetter, local) {
  if (store._wrappedGetters[type]) {
    // 重复定义判断
    if (process.env.NODE_ENV !== 'production') {
      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
    )
  }
}
6.resetStoreVM

installModules执行完成后,接下来到了最关键的一步,这是state状态响应式的关键;

resetStoreVm(this,state)
/**
* @param store Store对象
* @param state 完整的state状态
* @param hot
*/
function resetStoreVM (store, state, hot) {
  const oldVm = store._vm

  // bind store public getters
  store.getters = {}
  // reset local getters cache
  store._makeLocalGettersCache = Object.create(null)
  const wrappedGetters = store._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.
    // 这里使用闭包包裹getter中定义的函数是为了保留闭包中的参数Store
    /**
    * 相当于这样写
    * computed[key] = function(){
    *  // 这里要使用闭包,才能保留封闭环境中的参数store
    *     return store._wrappedGetters[key](store)
    * }
    */
    computed[key] = partial(fn, store)
    // 定义getters的取值,当我们调用this.$store.getters.some的时候,取的是store.vm.some的值
    // 且不可更改
    Object.defineProperty(store.getters, key, {
      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
  Vue.config.silent = true
  // 数据响应式的关键
  // 将state数据传入Vue实例中得到一个可响应的对象,即vuex实际上是维护了一个Vue实例,其中保存了一个单一的状态树,
  //然后vuex定义方法去修改这些状态;
  store._vm = new Vue({
    data: {
      $$state: state
    },
    computed
  })
  Vue.config.silent = silent
   
  // ...
}
7. makeLocalContext

maktLocalContext获取当前命名空间内的局部状态

function makeLocalContext (store, namespace, path) {
  const noNamespace = namespace === ''

  const local = {
    dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => {
      const args = unifyObjectStyle(_type, _payload, _options)
      const { payload, options } = args
      let { type } = args
      // 在局部命名空间内想通过commit触发全局空间的mutation,可以加上参数{ root: true }
      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) => {
      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: {
        // 获取局部命名空间下的state
      get: () => getNestedState(store.state, path)
    }
  })

  return local
}
8.commit

现在我们已经知道了所有的mutation处理函数都注册在store._mutations中,当我们通过commit去触发一个mutation的时候,只需要根据命名空间找到对应的处理函数并执行就好了

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]
    // ...
    this._withCommit(() => {
      entry.forEach(function commitIterator (handler) {
        handler(payload)
      })
    })
    // 执行subscribers
    // ...
  }
9.dispatch

dispatch也是同理

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

    const action = { type, payload }
    const entry = this._actions[type]
    // ... 容错处理
    
   // ...执行前置actionSubscribers
       
    const result = entry.length > 1
      ? Promise.all(entry.map(handler => handler(payload)))
      : entry[0](payload)

    return result.then(res => {
      // 后置_actionSubscribers 
      return res
    })
  }
10.动态注册

动态注册的module在注册时会以runtime = true为标记,只有runtime = true的module才可以unregister(卸载);

registerModule (path, rawModule, options = {}) {
    if (typeof path === 'string') path = [path]

    this._modules.register(path, rawModule)
    installModule(this, this.state, path, this._modules.get(path), options.preserveState)
    // reset store to update getters...
    resetStoreVM(this, this.state)
  }
// module的register方法
register (path, rawModule, runtime = true) {
    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)
      })
    }
  }