美文网首页
vuex源码阅读

vuex源码阅读

作者: 公子世无双ss | 来源:发表于2018-02-06 22:13 被阅读0次

    这几天忙啊,有绝地求生要上分,英雄联盟新赛季需要上分,就懒着什么也没写,很惭愧。这个vuex,vue-router,vue的源码我半个月前就看的差不多了,但是懒,哈哈。
    下面是vuex的源码分析
    在分析源码的时候我们可以写几个例子来进行了解,一定不要闭门造车,多写几个例子,也就明白了
    在vuex源码中选择了example/counter这个文件作为例子来进行理解
    counter/store.js是vuex的核心文件,这个例子比较简单,如果比较复杂我们可以采取分模块来让代码结构更加清楚,如何分模块请在vuex的官网中看如何使用。
    我们来看看store.js的代码:

    import Vue from 'vue'
    import Vuex from 'vuex'
    
    Vue.use(Vuex)
    
    const state = {
      count: 0
    }
    
    const mutations = {
      increment (state) {
        state.count++
      },
      decrement (state) {
        state.count--
      }
    }
    
    const actions = {
      increment: ({ commit }) => commit('increment'),
      decrement: ({ commit }) => commit('decrement'),
      incrementIfOdd ({ commit, state }) {
        if ((state.count + 1) % 2 === 0) {
          commit('increment')
        }
      },
      incrementAsync ({ commit }) {
        return new Promise((resolve, reject) => {
          setTimeout(() => {
            commit('increment')
            resolve()
          }, 1000)
        })
      }
    }
    
    const getters = {
      evenOrOdd: state => state.count % 2 === 0 ? 'even' : 'odd'
    }
    
    actions,
    
    export default new Vuex.Store({
      state,
      getters,
      actions,
      mutations
    })
    

    在代码中我们实例化了Vuex.Store,每一个 Vuex 应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的状态 。在源码中我们来看看Store是怎样的一个构造函数(因为代码太多,我把一些判断进行省略,让大家看的更清楚)

    Stor构造函数

    src/store.js

    export class Store {
      constructor (options = {}) {
        if (!Vue && typeof window !== 'undefined' && window.Vue) {
          install(window.Vue)
        }
    
        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()
    
        // bind commit and dispatch to self
        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
    
        installModule(this, state, [], this._modules.root)
    
        resetStoreVM(this, state)
    
        plugins.forEach(plugin => plugin(this))
    
        if (Vue.config.devtools) {
          devtoolPlugin(this)
        }
      }
    
      get state () {
        return this._vm._data.$$state
      }
    
      set state (v) {
        if (process.env.NODE_ENV !== 'production') {
          assert(false, `Use store.replaceState() to explicit replace store state.`)
        }
      }
    
      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'
          )
        }
      }
    
      dispatch (_type, _payload) {
        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
        }
    
        this._actionSubscribers.forEach(sub => sub(action, this.state))
    
        return entry.length > 1
          ? Promise.all(entry.map(handler => handler(payload)))
          : entry[0](payload)
      }
    
      subscribe (fn) {
        return genericSubscribe(fn, this._subscribers)
      }
    
      subscribeAction (fn) {
        return genericSubscribe(fn, this._actionSubscribers)
      }
    
      watch (getter, cb, options) {
        if (process.env.NODE_ENV !== 'production') {
          assert(typeof getter === 'function', `store.watch only accepts a function.`)
        }
        return this._watcherVM.$watch(() => getter(this.state, this.getters), cb, options)
      }
    
      replaceState (state) {
        this._withCommit(() => {
          this._vm._data.$$state = state
        })
      }
    
      registerModule (path, rawModule, options = {}) {
        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.`)
              assert(path.length > 0, 'cannot register the root module by using registerModule.')
          }
    
        this._modules.register(path, rawModule)
        installModule(this, this.state, path, this._modules.get(path), options.preserveState)
        resetStoreVM(this, this.state)
      }
    
      hotUpdate (newOptions) {
        this._modules.update(newOptions)
        resetStore(this, true)
      }
    
      _withCommit (fn) {
        const committing = this._committing
        this._committing = true
        fn()
        this._committing = committing
      }
    }
    

    首先判断window.Vue是否存在,如果不存在就安装Vue, 然后初始化定义,plugins表示应用的插件,strict表示是否为严格模式。然后对store
    的一系列属性进行初始化,来看看这些属性分别代表的是什么

    • _committing 表示一个提交状态,在Store中能够改变状态只能是mutations,不能在外部随意改变状态
    • _actions用来收集所有action,在store的使用中经常会涉及到的action
    • _actionSubscribers用来存储所有对 action 变化的订阅者
    • _mutations用来收集所有action,在store的使用中经常会涉及到的mutation
    • _wappedGetters用来收集所有action,在store的使用中经常会涉及到的getters
    • _modules 用来收集module,当应用足够大的情况,store就会变得特别臃肿,所以才有了module。每个Module都是单独的store,都有getter、action、mutation
    • _subscribers 用来存储所有对 mutation 变化的订阅者
    • _watcherVM 一个Vue的实例,主要是为了使用Vue的watch方法,观测数据的变化

    后面获取dispatch和commit然后将this.dipatch和commit进行绑定,我们来看看

    commit和dispatch

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

    commit有三个参数,_type表示mutation的类型,_playload表示额外的参数,options表示一些配置。unifyObjectStyle()这个函数就不列出来了,它的作用就是判断传入的_type是不是对象,如果是对象进行响应简单的调整。然后就是根据type来获取相应的mutation,如果不存在就报错,如果有就调用this._witchCommit。下面是_withCommit的具体实现

    _withCommit (fn) {
        const committing = this._committing
        this._committing = true
        fn()
        this._committing = committing
      }
    

    函数中多次提到了_committing,这个的作用我们在初始化Store的时候已经提到了更改状态只能通过mutatiton进行更改,其他方式都不行,通过检测committing的值我们就可以查看在更改状态的时候是否发生错误
    继续来看commit函数,this._withCommitting对获取到的mutation进行提交,然后遍历this._subscribers,调用回调函数,并且将state传入。总体来说commit函数的作用就是提交Mutation,遍历_subscribers调用回调函数
    下面是dispatch的代码

    dispatch (_type, _payload) {
    
        const action = { type, payload }
        const entry = this._actions[type]
    
        this._actionSubscribers.forEach(sub => sub(action, this.state))
    
        return entry.length > 1
          ? Promise.all(entry.map(handler => handler(payload)))
          : entry[0](payload)
      }
    }
    

    和commit函数类似,首先获取type对象的actions,然后遍历action订阅者进行回调,对actions的长度进行判断,当actions只有一个的时候进行将payload传入,否则遍历传入参数,返回一个Promise.
    commit和dispatch函数谈完,我们回到store.js中的Store构造函数
    this.strict表示是否开启严格模式,严格模式下我们能够观测到state的变化情况,线上环境记得关闭严格模式。
    this._modules.root.state就是获取根模块的状态,然后调用installModule(),下面是

    installModule()

    function installModule (store, rootState, path, module, hot) {
      const isRoot = !path.length
      const namespace = store._modules.getNamespace(path)
    
      // register in namespace map
      if (module.namespaced) {
        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)
        })
      }
    
      const local = module.context = makeLocalContext(store, namespace, path)
    
      module.forEachMutation((mutation, key) => {
        const namespacedType = namespace + key
        registerMutation(store, namespacedType, mutation, local)
      })
    
      module.forEachChild((child, key) => {
        installModule(store, rootState, path.concat(key), child, hot)
      })
    }
    
    

    installModule()包括5个参数store表示当前store实例,rootState表示根组件状态,path表示组件路径数组,module表示当前安装的模块,hot 当动态改变 modules 或者热更新的时候为 true
    首先进通过计算组件路径长度判断是不是根组件,然后获取对应的命名空间,如果当前模块存在namepaced那么就在store上添加模块
    如果不是根组件和hot为false的情况,那么先通过getNestedState找到rootState的父组件的state,因为path表示组件路径数组,那么最后一个元素就是该组件的路径,最后通过_withComment()函数提交Mutation,

    Vue.set(parentState, moduleName, module.state)
    

    这是Vue的部分,就是在parentState添加属性moduleName,并且值为module.state

    const local = module.context = makeLocalContext(store, namespace, path)
    

    这段代码里边有一个函数makeLocalContext
    makeLocalContext()


    function makeLocalContext (store, namespace, path) {
      const noNamespace = namespace === ''
    
      const local = {
        dispatch: noNamespace ? store.dispatch : ...
        commit: noNamespace ? store.commit : ...
      }
    
      Object.defineProperties(local, {
        getters: {
          get: noNamespace
            ? () => store.getters
            : () => makeLocalGetters(store, namespace)
        },
        state: {
          get: () => getNestedState(store.state, path)
        }
      })
    
      return local
    }
    

    传入的三个参数store, namspace, path,store表示Vuex.Store的实例,namspace表示命名空间,path组件路径。整体的意思就是本地化dispatch,commit,getter和state,意思就是新建一个对象上面有dispatch,commit,getter 和 state方法
    那么installModule的那段代码就是绑定方法在module.context和local上。继续看后面的首先遍历module的mutation,通过mutation 和 key 首先获取namespacedType,然后调用registerMutation()方法,我们来看看

    registerMutation()

    function registerMutation (store, type, handler, local) {
      const entry = store._mutations[type] || (store._mutations[type] = [])
      entry.push(function wrappedMutationHandler (payload) {
        handler.call(store, local.state, payload)
      })
    }
    

    是不是感觉这个函数有些熟悉,store表示当前Store实例,type表示类型,handler表示mutation执行的回调函数,local表示本地化后的一个变量。在最开始的时候我们就用到了这些东西,例如

    const mutations = {
      increment (state) {
        state.count++
      }
      ...
    

    首先获取type对应的mutation,然后向mutations数组push进去包装后的hander函数。

    module.forEachChild((child, key) => {
        installModule(store, rootState, path.concat(key), child, hot)
      })
    

    遍历module,对module的每个子模块都调用installModule()方法

    讲完installModule()方法,我们继续往下看resetStoreVM()函数

    resetStoreVM()

    function resetStoreVM (store, state, hot) {
      const oldVm = store._vm
      store.getters = {}
      const wrappedGetters = store._wrappedGetters
      const computed = {}
      forEachValue(wrappedGetters, (fn, key) => {
        computed[key] = () => fn(store)
        Object.defineProperty(store.getters, key, {
          get: () => store._vm[key],
          enumerable: true // for local getters
        })
      })
    
      const silent = Vue.config.silent
      Vue.config.silent = true
      store._vm = new Vue({
        data: {
          $$state: state
        },
        computed
      })
      Vue.config.silent = silent
    
      if (oldVm) {
        if (hot) {
         store._withCommit(() => {
            oldVm._data.$$state = null
          })
        }
        Vue.nextTick(() => oldVm.$destroy())
      }
    }
    

    resetStoreVM这个方法主要是重置一个私有的 _vm对象,它是一个 Vue 的实例。传入的有store表示当前Store实例,state表示状态,hot就不用再说了。对store.getters进行初始化,获取store._wrappedGetters并且用计算属性的方法存储了store.getters,所以store.getters是Vue的计算属性。使用defineProperty方法给store.getters添加属性,当我们使用store.getters[key]实际上获取的是store._vm[key]。然后用一个Vue的实例data来存储state 树,这里使用slient=true,是为了取消提醒和警告,在这里将一下slient的作用,silent表示静默模式(网上找的定义),就是如果开启就没有提醒和警告。后面的代码表示如果oldVm存在并且hot为true时,通过_witchCommit修改$$state的值,并且摧毁oldVm对象

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

    因为我们将state信息挂载到_vm这个Vue实例对象上,所以在获取state的时候,实际访问的是this._vm._data.$$state。

     watch (getter, cb, options) {
        return this._watcherVM.$watch(() => getter(this.state, this.getters), cb, options)
      }
    

    在wacher函数中,我们先前提到的this._watcherVM就起到了作用,在这里我们利用了this._watcherVM中$watch方法进行了数据的检测。watch 作用是响应式的监测一个 getter 方法的返回值,当值改变时调用回调。getter必须是一个函数,如果返回的值发生了变化,那么就调用cb回调函数。
    我们继续来将

    registerModule()

    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)
        resetStoreVM(this, this.state)
      }
    

    没什么难度就是,注册一个动态模块,首先判断path,将其转换为数组,register()函数的作用就是初始化一个模块,安装模块,重置Store._vm。

    因为文章写太长了,估计没人看,所以本文对代码进行了简化,有些地方没写出来,其他都是比较简单的地方。有兴趣的可以自己去看一下,最后补一张图,结合起来看源码更能事半功倍。这张图要我自己通过源码画出来,再给我看几天我也是画不出来的,所以还需要努力啊


    2761385260-5812e7755da76_articlex.png

    相关文章

      网友评论

          本文标题:vuex源码阅读

          本文链接:https://www.haomeiwen.com/subject/ymunzxtx.html