vuex源碼分析

你們好,今天給你們帶來的是vuex(2.3.1)源碼分析,但願可以能跟你們進行交流,歡迎提意見,寫的很差的地方歡迎拍磚

[github源碼地址][1]

首先咱們先來看看vuex是如何跟vue項目一塊兒結合使用的,如下是官方demo中的一個簡單例子
複製代碼

(1)咱們必須先建立一個storehtml

import Vue from 'vue'
import Vuex from 'vuex'
import { state, mutations } from './mutations'
import plugins from './plugins'

Vue.use(Vuex)

export default new Vuex.Store({
  state,
  mutations,
  plugins
})複製代碼

(2)將這個store傳給vue的實例,這樣咱們就可以在vue中獲取到這個store而且使用它的功能了vue

import 'babel-polyfill'
import Vue from 'vue'
import store from './store'
import App from './components/App.vue'

new Vue({
  store, // inject store to all children
  el: '#app',
  render: h => h(App)
})
複製代碼

以上就是vuex的簡單使用方法,而後接下來咱們就開始來分析vuex的源碼吧react

  • 目錄結構git

從目錄結構能夠看出,vuex是一個代碼比較簡潔的框架github

  • index.js——入口文件vuex

import { Store, install } from './store'
import { mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers } from './helpers'

export default {
  Store,
  install,
  version: '__VERSION__',
  mapState,
  mapMutations,
  mapGetters,
  mapActions,
  createNamespacedHelpers
}複製代碼

入口文件只作了一件事,就是導入了其餘相關的文件,而且將vuex的功能export出去,至關於定義vuex對外使用的API數組

  • store.js——vuex的倉庫,也是vuex中比較重要的一環
    這個文件比較長,咱們能夠一點一點來分析:
    整體來講,這個文件作了幾件事,定義並導出了Store這個類和install方法,並執行了install這個方法,咱們都知道,vue的全部插件都是經過install這個方法來安裝的promise

import applyMixin from './mixin'
import devtoolPlugin from './plugins/devtool'
import ModuleCollection from './module/module-collection'
import { forEachValue, isObject, isPromise, assert } from './util'複製代碼

一開始導入相關的方法,後面會解釋這些方法的用處緩存

let Vue // 定義了變量Vue,爲的是引用外部的vue構造函數,這樣vuex框架就能夠不用導入vue這個庫了複製代碼

----------------------------------------------------------這是分割線----------------------------------------------------------------------------------------bash

接下來是定義Store這個類,從圖中能夠看出這個vuex中的外store對外提供的能力,包括經常使用的commit,dispatch,watch等


準備上傳

先看看構造函數吧:

constructor (options = {}) {
    // 這個是在開發過程當中的一些環節判斷,vuex要求在建立vuex store實例以前必須先使用這個方法Vue.use(Vuex)來安裝vuex,項目必須也得支持promise,store也必須經過new來建立實例
    
    if (process.env.NODE_ENV !== 'production') {
      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.`)
    }
    
    // 從參數options中結構出相關變量
    const {
      plugins = [],
      strict = false
    } = options

    let {
      state = {}
    } = options
    
    // 這個簡單的,不解釋
    if (typeof state === 'function') {
      state = state()
    }

    // store internal state
    // 初始化store內部狀態,Object.create(null)能夠建立一個乾淨的空對象
    this._committing = false
    this._actions = Object.create(null)
    this._mutations = Object.create(null)
    this._wrappedGetters = Object.create(null)
    // vuex支持模塊,即將state經過key-value的形式拆分爲多個模塊
    // 模塊的具體內容能夠查看這裏 :https://vuex.vuejs.org/en/mutations.html
    this._modules = new ModuleCollection(options)
    this._modulesNamespaceMap = Object.create(null)
    // 監聽隊列,當執行commit時會執行隊列中的函數
    this._subscribers = []
    // 建立一個vue實例,利用vue的watch的能力,能夠監控state的改變,具體後續watch方法會介紹
    this._watcherVM = new Vue()

    // bind commit and dispatch to self
    
    const store = this
    // 緩存dispatch和commit方法
    const { dispatch, commit } = this
    // 定義dispatch方法
    this.dispatch = function boundDispatch (type, payload) {
      return dispatch.call(store, type, payload)
    }
    // 定義commit方法
    this.commit = function boundCommit (type, payload, options) {
      return commit.call(store, type, payload, options)
    }

    // strict mode
    // 定義嚴格模式,不要在發佈環境下啓用嚴格模式!嚴格模式會深度監測狀態樹來檢測不合規的狀態變動——請確保在發佈環境下關閉嚴格模式,以免性能損失。
    // 具體後續enableStrictMode方法會提到
    this.strict = strict

    // init root module.
    // this also recursively registers all sub-modules
    // and collects all module getters inside this._wrappedGetters
    // 這個做者的註釋已經寫得挺明白,就是初始化根模塊,遞歸註冊子模塊,收集getter
    installModule(this, state, [], this._modules.root)

    // initialize the store vm, which is responsible for the reactivity
    // (also registers _wrappedGetters as computed properties)
    // 初始化store中的state,使得state變成響應式的,原理就是將state做爲一個vue實例的data屬性傳入,具體在分析這個函數的時候會介紹
    resetStoreVM(this, state)

    // apply plugins
    // 執行插件,這個是一個數組,因此遍歷他,而後執行每一個插件的函數
    plugins.concat(devtoolPlugin).forEach(plugin => plugin(this))
  }複製代碼

呼呼呼~ 至此,終於把store類所有讀完了,休息五分鐘,而後繼續往下看哈。

接下來關於state的獲取和設置

// 獲取state,  直接返回內部data的$$state
  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是vuex中一個比較重要的操做,由於它能夠觸發mutation修改對state的修改,而且是同步執行的

commit (_type, _payload, _options) {
    // check object-style commit
    // 首先統一傳入參數的格式,主要是針對當type是個對象的狀況,須要把這個對象解析出來
    const {
      type,
      payload,
      options
    } = unifyObjectStyle(_type, _payload, _options)
    
    // 緩存本次commit操做的類型和負荷,以供後續監聽隊列(this._subscribers)使用
    const mutation = { type, payload }
    // 獲取相關的type的mutation函數,咱們都知道,在vuex中都是經過commit一個類型而後觸發相關的mutation函數來操做state的,因此在此必須獲取相關的函數
    const entry = this._mutations[type]
    if (!entry) {
      if (process.env.NODE_ENV !== 'production') {
        console.error(`[vuex] unknown mutation type: ${type}`)
      }
      return
    }
    // 在_withCommit中觸發上面獲取的mutation函數,簡單粗暴使用數組的forEach執行哈哈,之因此要在外面包一層_withCommit,是代表操做的同步性
    this._withCommit(() => {
      entry.forEach(function commitIterator (handler) {
        handler(payload)
      })
    })
    // 這個就是以前說的監聽隊列,在每次執行commit函數時都會遍歷執行一下這個隊列
    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是跟commit有點類似的函數,可是commit必須是同步的,而dispatch是異步的,內部仍是必須經過commit來操做state

dispatch (_type, _payload) {
    // check object-style dispatch
    // 同上面commit,不解釋
    const {
      type,
      payload
    } = unifyObjectStyle(_type, _payload)
    
    // 由於dispatch觸發的是actions中的函數,因此這裏獲取actions相關函數,過程相似commit
    const entry = this._actions[type]
    if (!entry) {
      if (process.env.NODE_ENV !== 'production') {
        console.error(`[vuex] unknown action type: ${type}`)
      }
      return
    }
    // 由於dispatch支持異步,因此這裏做者使用Promise.all來執行異步函數而且判斷全部異步函數是否都已經執行完成,因此在文件最開始判斷了當前環境必須支持promise就是這個緣由
    return entry.length > 1
      ? Promise.all(entry.map(handler => handler(payload)))
      : entry[0](payload)
  }複製代碼

subscribe函數,這是pub/sub模式在vuex中的一個運用,用戶能夠經過subscribe函數來監聽state的變化,函數返回一個取消監聽的函數,便於用戶在合適的時機取消訂閱

subscribe (fn) {
    const subs = this._subscribers
    if (subs.indexOf(fn) < 0) {
      subs.push(fn)
    }
    // 返回取消訂閱的函數,經過函數額splice方法,來清除函數中不須要的項
    return () => {
      const i = subs.indexOf(fn)
      if (i > -1) {
        subs.splice(i, 1)
      }
    }
  }複製代碼

watch函數,響應式地監測一個 getter 方法的返回值,當值改變時調用回調函數,原理其實就是利用vue中的watch方法

watch (getter, cb, options) {
    if (process.env.NODE_ENV !== 'production') {
      assert(typeof getter === 'function', `store.watch only accepts a function.`)
    }
    // 在上面構造函數中,咱們看到this._watcherVM就是一個vue的實例,因此能夠利用它的watch來實現vuex的watch,原理都同樣,當監聽的值或者函數的返回值發送改變的時候,就觸發相應的回調函數,也就是咱們傳入的cb參數,options則能夠來讓監聽當即執行&深度監聽對象
    return this._watcherVM.$watch(() => getter(this.state, this.getters), cb, options)
  }複製代碼

replaceState,根據名字就可知道,是替換當前的state

replaceState (state) {
    this._withCommit(() => {
      this._vm._data.$$state = state
    })
  }複製代碼

registerModule函數,可使用 store.registerModule 方法註冊模塊

registerModule (path, rawModule) {
    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.')
    }
    //其實內部時候經過,register方法,遞歸尋找路徑,而後將新的模塊註冊root模塊上,具體後續介紹到module的時候會詳細分析
    this._modules.register(path, rawModule)
    //安裝模塊,由於每一個模塊都有他自身的getters,actions, modules等,因此,每次註冊模塊都必須把這些都註冊上,後續介紹installModule的時候,會詳細介紹到
    installModule(this, this.state, path, this._modules.get(path))
    // reset store to update getters...
    // 重置VM
    resetStoreVM(this, this.state)
  }複製代碼

unregisterModule函數,上述registerModule函數的相反操做,具體在module的時候會介紹到,在此瞭解個大概,先不糾結細節

unregisterModule (path) {
    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.`)
    }

    this._modules.unregister(path)
    this._withCommit(() => {
      const parentState = getNestedState(this.state, path.slice(0, -1))
      // 利用vue.delete方法,確保模塊在被刪除的時候,視圖能監聽到變化
      Vue.delete(parentState, path[path.length - 1])
    })
    resetStore(this)
  }複製代碼

hotUpdate函數,Vuex 支持在開發過程當中熱重載 mutation、modules、actions、和getters

hotUpdate (newOptions) {
    this._modules.update(newOptions)
    resetStore(this, true)
  }複製代碼

_withCommit函數,從函數名能夠看出這是一個內部方法,做用就是保證commit過程當中執行的方法都是同步的

_withCommit (fn) {
    // 保存原來的committing的狀態
    const committing = this._committing
    //將想在的committing狀態設置爲true
    this._committing = true
    //執行函數
    fn()
    //將committing狀態設置爲原來的狀態
    this._committing = committing
  }複製代碼

到目前爲止,咱們已經看完了Store這個類的全部代碼~慢慢消化,而後繼續往下

----------------------------------------------------------這又是分割線----------------------------------------------------------------------------------------

接下來,咱們分析一下,一些其餘的輔助方法,跟上面store的一些內容會有相關。ready? Go

resetStore函數,用於重置整個vuex中的store,從代碼中能夠看出,這個函數主要的功能,就是將傳入的store實例的_actions,_mutations,_wrappedGetters,_modulesNamespaceMap置爲空,而後從新安裝模塊和重置VM,此方法在上述熱更新和註銷模塊的時候會使用到

function resetStore (store, hot) {
  store._actions = Object.create(null)
  store._mutations = Object.create(null)
  store._wrappedGetters = Object.create(null)
  store._modulesNamespaceMap = Object.create(null)
  const state = store.state
  // init all modules
  installModule(store, state, [], store._modules.root, true)
  // reset vm
  resetStoreVM(store, state, hot)
}複製代碼

resetStoreVM函數,這個用於重置store中的vm,所謂vm,指的就是視圖模型,也就是常見mvvm中的vm,在此指的是將state做爲data中$$state屬性的一個vue實例

function resetStoreVM (store, state, hot) {
   // 保存原有store的_vm
  const oldVm = store._vm
    
  // bind store public getters
  store.getters = {}
  // store的_wrappedGetters緩存了當前store中全部的getter
  const wrappedGetters = store._wrappedGetters
  const computed = {}
  //遍歷這個對象,獲取每一個getter的key和對應的方法
  forEachValue(wrappedGetters, (fn, key) => {
    // use computed to leverage its lazy-caching mechanism
    // 將getter以key-value的形式緩存在變量computed中,其實後面就是將getter做爲vue實例中的計算屬性
    computed[key] = () => fn(store)
    // 當用戶獲取getter時,至關於獲取vue實例中的計算屬性,使用es5的這個Object.defineProperty方法作一層代理
    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
  // silent設置爲true,則取消了全部的警告和日誌,眼不見爲淨
  Vue.config.silent = true
  
  // 將傳入的state,做爲vue實例中的data的$$state屬性,將剛剛使用computed變量蒐集的getter,做爲實例的計算屬性,因此當state和getter都變成了響應式的了
  store._vm = new Vue({
    data: {
      $$state: state
    },
    computed
  })
  Vue.config.silent = silent

  // enable strict mode for new vm
    
  if (store.strict) {
    //若是設置了嚴格模式則,不容許用戶在使用mutation之外的方式去修改state
    enableStrictMode(store)
  }

  if (oldVm) {
    if (hot) {
      // dispatch changes in all subscribed watchers
      // to force getter re-evaluation for hot reloading.
      store._withCommit(() => {
        // 將原有的vm中的state設置爲空,因此原有的getter都會從新計算一遍,利用的就是vue中的響應式,getter做爲computed屬性,只有他的依賴改變了,纔會從新計算,而如今把state設置爲null,因此計算屬性從新計算
        oldVm._data.$$state = null
      })
    }
    // 在下一次週期銷燬實例
    Vue.nextTick(() => oldVm.$destroy())
  }
}複製代碼

installModule函數,用於安裝模塊,註冊相應的mutation,action,getter和子模塊等

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) {
    // 將模塊的state設置爲響應式
    const parentState = getNestedState(rootState, path.slice(0, -1))
    const moduleName = path[path.length - 1]
    store._withCommit(() => {
      Vue.set(parentState, moduleName, module.state)
    })
  }
  //設置本地上下文,主要是針對模塊的命名空間,對dispatch,commit,getters和state進行修改,後面講到makeLocalContext的時候會詳細分析,如今只須要知道,這個操做讓用戶可以直接獲取到對象子模塊下的對象就能夠了
  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 namespacedType = namespace + key
    registerAction(store, namespacedType, action, 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)
  })
}複製代碼

makeLocalContext函數,就是installModule中設置本地上下文的具體實現

function makeLocalContext (store, namespace, path) {
   //若是沒有命名空間,則是使用全局store上的屬性,不然對store上的屬性進行本地化處理
  const noNamespace = namespace === ''

  const local = {
    dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => {
      //dispatch的本地化處理,就是修改type
      const args = unifyObjectStyle(_type, _payload, _options)
      const { payload, options } = args
      let { type } = args

      if (!options || !options.root) {
         //在type前面加上命名空間
        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
        }
      }
        //調用store上的dispatch方法
      return store.dispatch(type, payload)
    },

    commit: noNamespace ? store.commit : (_type, _payload, _options) => {
    // commit的本地化修改跟dispatch類似,也是隻是修改了type,而後調用store上面的commit
      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
   //gettters和state的修改,則依賴於makeLocalGetters函數和getNestedState函數,後面會分析
  Object.defineProperties(local, {
    getters: {
      get: noNamespace
        ? () => store.getters
        : () => makeLocalGetters(store, namespace)
    },
    state: {
      get: () => getNestedState(store.state, path)
    }
  })

  return local
}複製代碼

makeLocalGetters函數,則是對getter進行本地化處理

function makeLocalGetters (store, namespace) {
  const gettersProxy = {}

  const splitPos = namespace.length
  Object.keys(store.getters).forEach(type => {
    //這裏獲取的每一個type都是一個有命名空間+本地type的字符串,例如: type的值可能爲 「m1/m2/」+"typeName"
    // skip if the target getter is not match this namespace
    if (type.slice(0, splitPos) !== namespace) return

    // extract local getter type
    const localType = type.slice(splitPos)

    // Add a port to the getters proxy.
    // Define as getter property because
    // we do not want to evaluate the getters in this time.
    //至關於作了一層代理,將子模塊的localType映射到store上的type
    Object.defineProperty(gettersProxy, localType, {
      get: () => store.getters[type],
      enumerable: true
    })
  })

  return gettersProxy
}複製代碼

registerMutation函數,就是註冊mutation的過程,將相應type的mutation推到store._mutations[type]的隊列中,當commit這個type的時候就觸發執行隊列中的函數

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

registerAction函數,註冊action的過程,原理相似於registerMutation,不一樣點在於action支持異步,因此必須用promise進行包裝

function registerAction (store, type, handler, local) {
  const entry = store._actions[type] || (store._actions[type] = [])
  entry.push(function wrappedActionHandler (payload, cb) {
    let res = handler({
      dispatch: local.dispatch,
      commit: local.commit,
      getters: local.getters,
      state: local.state,
      rootGetters: store.getters,
      rootState: store.state
    }, payload, cb)
    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
    }
  })
}複製代碼

registerGetters函數,根據type,將getter方法掛載在store._wrappedGetters[type]下面

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) {
    // 爲子模塊的getter提供了這個四個參數,方便用戶獲取,若是是根模塊,則local跟store取出來的state和getters相同
    return rawGetter(
      local.state, // local state
      local.getters, // local getters
      store.state, // root state
      store.getters // root getters
    )
  }
}複製代碼

enableStrictMode函數則是在嚴格模式下,不容許state被除mutation以外的其餘操做修改,代碼比較簡單,利用vue的$watch方法實現的

function enableStrictMode (store) {
  store._vm.$watch(function () { return this._data.$$state }, () => {
    if (process.env.NODE_ENV !== 'production') {
      assert(store._committing, `Do not mutate vuex store state outside mutation handlers.`)
    }
  }, { deep: true, sync: true })
}複製代碼

getNestedState函數,獲取對應路徑下的state

function getNestedState (state, path) {
  return path.length
    ? path.reduce((state, key) => state[key], state)
    : state
}複製代碼

unifyObjectStyle函數,做用是調整參數,主要是當type是一個對象的時候,對參數進行調整

function unifyObjectStyle (type, payload, options) {
  if (isObject(type) && type.type) {
    options = payload
    payload = type
    type = type.type
  }

  if (process.env.NODE_ENV !== 'production') {
    assert(typeof type === 'string', `Expects string as the type, but found ${typeof type}.`)
  }

  return { type, payload, options }
}複製代碼

以上是相關輔助函數的所有內容,你看明白了麼~

----------------------------------------------------------這依然是分割線------------------------------------------------------------------------------------

文件的最後,就是定義了install函數,而後自動執行了這個函數,讓vuex可以在項目中運做起來

export function install (_Vue) {
  if (Vue) {
    if (process.env.NODE_ENV !== 'production') {
      console.error(
        '[vuex] already installed. Vue.use(Vuex) should be called only once.'
      )
    }
    return
  }
  Vue = _Vue
  //在vue的生命週期中初始化vuex,具體實現後面講到mixin.js這個文件時會說明
  applyMixin(Vue)
}

// auto install in dist mode
if (typeof window !== 'undefined' && window.Vue) {
  install(window.Vue)
}複製代碼

以上就是store.js的全部內容啦~

----------------------------------------------------------嚴肅分割線------------------------------------------------------------------------------------

接下來說解有關module目錄下的內容,該目錄有兩個文件分別是module-collection.js和module.js,這兩個文件主要是有關於vuex中模塊的內容;
首先咱們看看module-collection.js,這個文件主要導出一個ModuleCollection類:
構造函數

constructor (rawRootModule) {
    // register root module (Vuex.Store options)
    //主要是註冊根模塊,咱們在以前store的構造函數中曾經使用到 this._modules = new ModuleCollection(options),註冊一個根模塊而後緩存在this._module中
    this.register([], rawRootModule, false)
  }複製代碼

緊接着看看下面register函數,它用於註冊模塊

register (path, rawModule, runtime = true) {
    if (process.env.NODE_ENV !== 'production') {
      assertRawModule(path, rawModule)
    }
    // 建立一個新模塊,具體會在後面講到Module的時候分析
    const newModule = new Module(rawModule, runtime)
    // 判讀是否爲根模塊
    if (path.length === 0) {
      this.root = newModule
    } else {
      //根據path路徑,利用get方法獲取父模塊  
      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)
      })
    }
  }複製代碼

相反,unregister函數則是移除一個模塊

unregister (path) {
    // 經過get方法獲取父模塊
    const parent = this.get(path.slice(0, -1))
    //獲取須要刪除的模塊的名稱,即他的key
    const key = path[path.length - 1]
    if (!parent.getChild(key).runtime) return
    //利用module中removeChild方法刪除該模塊,其實就是delete了對象上的一個key
    parent.removeChild(key)
  }複製代碼

get函數,其實就是利用es5中數組reduce方法,從根模塊開始根據傳入的path來獲取相應的子模塊

get (path) {
    return path.reduce((module, key) => {
      return module.getChild(key)
    }, this.root)
  }複製代碼

getNamespace函數,利用傳入的參數path,生成相應的命名空間,實現的原理跟上述的get方法相似

getNamespace (path) {
    let module = this.root
    return path.reduce((namespace, key) => {
      module = module.getChild(key)
      return namespace + (module.namespaced ? key + '/' : '')
    }, '')
  }複製代碼

upate方法,就是更新模塊,具體看下面update方法的實現

update (rawRootModule) {
    update([], this.root, rawRootModule)
  }複製代碼

以上就是整個ModuleCollection類的實現

接下來說解一下function update的實現

function update (path, targetModule, newModule) {
  if (process.env.NODE_ENV !== 'production') {
    assertRawModule(path, newModule)
  }

  // update target module
  //目標模塊更新爲新模塊,具體實現是將原有模塊的namespaced,actions,mutations,getters替換爲新模塊的namespaced,actions,mutations,getters
  // 具體會在Module類中update方法講解
  targetModule.update(newModule)

  // update nested modules
  // 若是新的模塊有子模塊,則遞歸更新子模塊
  if (newModule.modules) {
    for (const key in newModule.modules) {
      if (!targetModule.getChild(key)) {
        if (process.env.NODE_ENV !== 'production') {
          console.warn(
            `[vuex] trying to add a new module '${key}' on hot reloading, ` +
            'manual reload is needed'
          )
        }
        return
      }
      update(
        path.concat(key),
        targetModule.getChild(key),
        newModule.modules[key]
      )
    }
  }
}複製代碼

至於assertRawModule方法和makeAssertionMessage方法,就是一些簡單的校驗和提示,不影響主流程&代碼比較簡單,這裏不作贅述

以上就是整個module-collection.js文件的全部內容

接下來就應該分析目錄中的另外一個文件module.js,這個文件主要導出一個Module類,這個類主要描述了vuex中模塊的功能

構造函數,主要作了一些模塊初始化的事情

//構造函數,主要作了一些模塊初始化的事情
  constructor (rawModule, runtime) {
    //緩存運行時的標誌
    this.runtime = runtime
    //建立一個空對象來保存子模塊
    this._children = Object.create(null)
    //緩存傳入的模塊
    this._rawModule = rawModule
    //緩存傳入模塊的state,若是state是一個函數,則執行這個函數
    const rawState = rawModule.state
    this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}
  }複製代碼

namespaced函數是主要就是獲取當前模塊是不是命名模塊,vuex支持命名模塊和匿名模塊

get namespaced () {
    return !!this._rawModule.namespaced
  }複製代碼

addChild,removeChild,getChild這三個函數就分別是添加,刪除,獲取子模塊,內容比較簡單,不贅述

update方法,將原有緩存模塊的namespaced,actions,mutations,getters替換成新傳入模塊的namespaced,actions,mutations,getters

update (rawModule) {
    this._rawModule.namespaced = rawModule.namespaced
    if (rawModule.actions) {
      this._rawModule.actions = rawModule.actions
    }
    if (rawModule.mutations) {
      this._rawModule.mutations = rawModule.mutations
    }
    if (rawModule.getters) {
      this._rawModule.getters = rawModule.getters
    }
  }複製代碼

forEachChild函數,利用util中forEachValue方法,變量每一個子模塊,將每一個子模塊做爲傳入的回調函數參數,而後執行回調函數

forEachChild (fn) {
    forEachValue(this._children, fn)
  }複製代碼

forEachGetter,forEachAction,forEachMutation代碼邏輯跟上述forEachChild十分相似,不在贅述

以上就是module.js文件的全部內容,至此咱們也已經所有分析完module目錄下的全部代碼了

---------------------------------------------------一本正經分割線--------------------------------------------------------------------------------
接下來,咱們再看看help.js這個文件,這個文件主要是提供了一些幫助性的方法,使得用戶在使用vuex的過程當中體驗更好,更加方便

首先咱們先看看文件後面三個函數:normalizeMap,normalizeNamespace,getModuleByNamespace

normalizeMap函數,這個方法的做用是格式化傳入的對象

function normalizeMap (map) {
  // 若是傳入的對象是數組,則放回一個每一項都是key-val對象的數組,其中key和val的值相同
  // 若是出入的是一個對象,則變量這個對象,放回一個每一項都是key-val數組,其中key對應對象的key,val對應相應key的值
  return Array.isArray(map)
    ? map.map(key => ({ key, val: key }))
    : Object.keys(map).map(key => ({ key, val: map[key] }))
}複製代碼

normalizeNamespace函數,調整參數,格式化命名空間

function normalizeNamespace (fn) {
  return (namespace, map) => {
    //若是沒傳入命名空間,或者傳入的命名空間不是一個字符串,則丟棄該參數
    if (typeof namespace !== 'string') {
      map = namespace
      namespace = ''
    } else if (namespace.charAt(namespace.length - 1) !== '/') {
      //不然判斷命名空間後面是否有加上‘/’,若是沒有則加上
      namespace += '/'
    }
    //最後執行傳入的回調函數
    return fn(namespace, map)
  }
}複製代碼

getModuleByNamespace函數,經過命名空間來獲取模塊

function getModuleByNamespace (store, helper, namespace) {
  // 返回store._modulesNamespaceMap緩存的模塊
  const module = store._modulesNamespaceMap[namespace]
  if (process.env.NODE_ENV !== 'production' && !module) {
    console.error(`[vuex] module namespace not found in ${helper}(): ${namespace}`)
  }
  return module
}複製代碼

mapState函數,咱們能夠經過這個方法將state解構到vue項目中去,使其變成vue實例中的計算屬性

export const mapState = normalizeNamespace((namespace, states) => {
  //定義一個空對象
  const res = {}
  normalizeMap(states).forEach(({ key, val }) => {
    //收集states的全部key,對應key的值,改變成一個mappedState方法,符合計算屬性的特色
    res[key] = function mappedState () {
      //獲取store的state和getters
      let state = this.$store.state
      let getters = this.$store.getters
      //若是存在命名空間,則將命名空間下子模塊的state和getters覆蓋原來store的state和getters
      if (namespace) {
        const module = getModuleByNamespace(this.$store, 'mapState', namespace)
        if (!module) {
          return
        }
        state = module.context.state
        getters = module.context.getters
      }
      //若是對應的val是函數則執行,不然返回state下的值
      return typeof val === 'function'
        ? val.call(this, state, getters)
        : state[val]
    }
    // mark vuex getter for devtools
    res[key].vuex = true
  })
  //返回這個包裝過state的對象,這個對象能夠結構成vue中的計算屬性
  return res
})複製代碼

mapMutations函數,則是將mutation解構到vue實例中的methods中,使得用戶能夠直接調用methods中的方法來執行store.commit

export const mapMutations = normalizeNamespace((namespace, mutations) => {
  //定義一個空對象
  const res = {}
  normalizeMap(mutations).forEach(({ key, val }) => {
    val = namespace + val
    res[key] = function mappedMutation (...args) {
      if (namespace && !getModuleByNamespace(this.$store, 'mapMutations', namespace)) {
        return
      }
      //調用了store中的commit方法,觸發相應的mutation函數的執行
      return this.$store.commit.apply(this.$store, [val].concat(args))
    }
  })
  return res
})複製代碼

mapGetters的邏輯跟mapState相似,mapActions的邏輯跟mapMutations相似,這裏再也不贅述

自此,咱們把help.js的內容也分析完了

相關文章
相關標籤/搜索