Vuex源码解析

163 阅读3分钟

Vuex

Vuex 是一个专为Vue.js开发的状态管理插件。state定义了数据源,mutation,action等实现了对数据的管理。

Vuex插件引入以及store的注入

在vue项目内通过 Vue.use(Vuex)引入。Vue.use()调用Vuex的install方法。

install 在文件 src\store.js

export function install (_Vue) {
  Vue = _Vue
  applyMixin(Vue)
}

applyMixin 在文件 src\mixin.js

export default function (Vue) {
  //	全局混入,在每个组件的beforeCreate生命周期执行vuexInit
  Vue.mixin({ beforeCreate: vuexInit })

  function vuexInit () {
    const options = this.$options
    
    if (options.store) {
      //	根组件注入store
      this.$store = typeof options.store === 'function'
        ? options.store()
        : options.store
    } else if (options.parent && options.parent.$store) {
      //	子组件注入store
      //	根据生命周期父组件的beforeCreate在子组件之前执行,保证了子组件也可以注入store
      this.$store = options.parent.$store
    }
  }
}

Store初始化

class Store {
  constructor(options = {}) {
    this._committing = false	//	判断是否通过commit修改state
    this._actions = Object.create(null)	//	actions的类型和对应的函数
    this._actionSubscribers = []	//	dispatch时要触发的函数
    this._mutations = Object.create(null)	//	mutations的类型和对应的函数
    this._wrappedGetters = Object.create(null)	//	getters的类型和对应的函数
    this._modules = new ModuleCollection(options)	//	module数据结构转换
    this._modulesNamespaceMap = Object.create(null)	// module名字和对应的module
    this._subscribers = []	//	commit时要触发的函数
    this._watcherVM = new Vue()
    this._makeLocalGettersCache = Object.create(null)

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

    const state = this._modules.root.state

    //	注册module
    installModule(this, state, [], this._modules.root)

    resetStoreVM(this, state)

    plugins.forEach(plugin => plugin(this))
  }
}

installModule:

function installModule (store, rootState, path, module, hot) {
  const isRoot = !path.length
  //  获取命名空间(namespace)
  const namespace = store._modules.getNamespace(path)

  //  注册module
  if (module.namespaced) {
    store._modulesNamespaceMap[namespace] = module
  }

  if (!isRoot && !hot) {
    const parentState = getNestedState(rootState, path.slice(0, -1))
    const moduleName = path[path.length - 1]
    store._withCommit(() => {
      if (__DEV__) {
        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)

  // 注册muatation
  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)
  })
}

resetStoreVM内将数据变成响应式:

function resetStoreVM (store, state, hot) {
  store.getters = {}
  store._makeLocalGettersCache = Object.create(null)
  const wrappedGetters = store._wrappedGetters
  const computed = {}
  //	将getter转换成computed格式,并代理到store
  forEachValue(wrappedGetters, (fn, key) => {
    computed[key] = partial(fn, store)
    Object.defineProperty(store.getters, key, {
      get: () => store._vm[key],
      enumerable: true
    })
  })

  // 将state变成响应式
  store._vm = new Vue({
    data: {
      $$state: state
    },
    computed
  })
}

代理,使this.$store.state获取的是store._vm._data.$$state

 get state () {
    return this._vm._data.$$state
  }

注册监听(subscribe,subscribeAction)

subscribe在src\store.js

 subscribe (fn, options) {
    return genericSubscribe(fn, this._subscribers, options)
  }

subscribeAction在src\store.js

subscribeAction (fn, options) {
    //	如果是函数则添加增加before属性,也可以传入{ after: fn },{ error: fn }
    //	当触发action时,会根据before,after,error属性在不同的时机执行fn
    const subs = typeof fn === 'function' ? { before: fn } : fn
    return genericSubscribe(subs, this._actionSubscribers, options)
  }

subscribe,subscribeAction都会去调用genericSubscribe去注册监听。

subscribe注册的监听只会在触发commit时执行。

subscribeAction注册的监听只会在触发dispatch时执行。

genericSubscribe在src\store.js

function genericSubscribe (fn, subs, options) {
  //	判断监听列表里是否已经有该函数,没有则添加
  //	按照配置项将函数添加至首部还是尾部
  if (subs.indexOf(fn) < 0) {
    options && options.prepend
      ? subs.unshift(fn)
      : subs.push(fn)
  }
  //	返回取消监听的方法
  return () => {
    const i = subs.indexOf(fn)
    if (i > -1) {
      subs.splice(i, 1)
    }
  }
}

触发action(dispatch)

dispatch(type, payload) {
  const action = { type, payload }
  //  获取对应action类型的函数
  const entry = this._actions[type]

  //  将准备在action触发前的函数全部执行
  //	异步,不会等待before,after,error执行完之后才执行后续的步骤
  this._actionSubscribers.slice().filter(sub => sub.before).forEach(sub => sub.before(action, this.state))

  //  执行对应action类的函数
  const result = entry.length > 1
    ? Promise.all(entry.map(handler => handler(payload)))
    : entry[0](payload)

  return new Promise((resolve, reject) => {
    result.then(res => {
      //  将准备在action触发后的函数全部执行
      this._actionSubscribers.filter(sub => sub.after).forEach(sub => sub.after(action, this.state))
      resolve(res)
    }, error => {
      //  将准备在action异常时的函数全部执行
      this._actionSubscribers.filter(sub => sub.error).forEach(sub => sub.error(action, this.state, error))
      reject(error)
    })
  })
}

触发mutation(commit)

commit(type, payload, options) {
  const mutation = { type, payload }
  //  获取对应mutations类型的函数
  const entry = this._mutations[type]

  //	执行真正对应mutations类型的函数
  this._withCommit(() => {
    entry.forEach(function commitIterator (handler) {
      handler(payload)
    })
  })

  //	执行subscribe注册的函数
  this._subscribers.slice().forEach(sub => sub(mutation, this.state))
}

真正的函数在_withCommit函数内执行。

_withCommit在src\store.js

_withCommit (fn) {
    //	因为vuex规定通过commit来修改store,但是用户也可以直接修改store
    //	所以此处通过commiting用于判断用户用哪种方式进行修改
    //	vuex监听了store的状态变化,如果在严格模式的开发模式下直接修改store,控制台会提示;该功能通过enableStrictMode实现
    const committing = this._committing
    this._committing = true
    fn()
    this._committing = committing

enableStrictMode在src\store.js

function enableStrictMode (store) {
  //	通过vue的watch监听state
  store._vm.$watch(function () { return this._data.$$state }, () => {
    if (__DEV__) {
      //	在开发模式下,如果store的值更改了,且_committing为true,就会在控制台进行提示
      assert(store._committing, `do not mutate vuex store state outside mutation handlers.`)
    }
  }, { deep: true, sync: true })
}

辅助函数(mapState,mapMutations,mapActions,mapGetters)

mapState:将对应state内的属性返回,和vue内的computed合并

mapGetters:将对应的getter返回,和vue内的computed合并

mapActions:将对应的action返回,和vue内的method合并

mapMutation:将对应的mutation返回,和vue内的method合并

以mapState为例子,其他同理:

// 举个栗子
//	多模块的两种取值写法
// computed: {
//   ...mapState({
//     list: state => state.cart.list
//   }),
//   // ...mapState('cart', ['list'])
// },

const mapState = (namespace, map) => {
  //	第一种写法:
  //	namesapce:{list: state => state.cart.list}
  //	map:undefined
  if (typeof namespace !== 'string') {
    map = namespace
    namespace = ''
  } else if (namespace.charAt(namespace.length - 1) !== '/') {
    namespace += '/'
  }
  //	map:{list: state => state.cart.list}
  
  return (namespace, states) => {
    const res = {}
    //	normalizeMap(states)=>[{key:'list'},val:state => state.cart.list]
    normalizeMap(states).forEach(({ key, val }) => {
      res[key] = function mappedState () {
        //	获取state和getter
        let state = this.$store.state
        let getters = this.$store.getters
        //	如果有命名空间,就去对应的命名文件内找state和getter
        if (namespace) {
          const module = getModuleByNamespace(this.$store, 'mapState', namespace)
          if (!module) {
            return
          }
          state = module.context.state
          getters = module.context.getters
        }
         //	根据写法获取对应的value值
        return typeof val === 'function'
          ? val.call(this, state, getters)
          : state[val]
      }
      res[key].vuex = true
    })
    return res
  }
}

function normalizeMap (map) {
  if (!isValidMap(map)) {
    return []
  }
  //	根据写法获取对应的key值
  return Array.isArray(map)
    ? map.map(key => ({ key, val: key }))
    : Object.keys(map).map(key => ({ key, val: map[key] }))
}

总结

  1. vuex流程:

定义state,通过dispatch(有异步逻辑情况下使用,会调用action)或者commit(会调用mutation)方法去修改值。也可以通过mapActions或者mapMutation获取对应的action和commit方法,再去修改。

getter相当于state的计算属性。

  1. vuex可以直接修改state的值,且触发响应式;但是规范要求最好不要直接修改,而且在严格模式下控制台会报错。
  2. vuex可以在mutation内执行异步操作,但是规范要求在action内执行异步操作;因为会导致数据混乱,难以调试。
  1. vuex响应式原理:借用vue的响应式原理,将state绑定到data上,getter绑定到computed上。
  2. 各组件都可以访问到store的原因:在插件被调用时会将beforeCreate混入到各个组件,在执行beforeCreate时会将store绑定到各个组件上。
  3. mapActions,mapMutation,mapGetters,mapState本质上就是获取对应的属性,再和组件内的method,computed对象合并。