vuex源碼分析(一)

vuex的源碼分析系列大概會分爲三篇博客來說,爲何分三篇呢,由於寫一篇太多了。您看着費勁,我寫着也累
圖片描述前端

這是vuex的目錄圖
圖片描述vue

分析vuex的源碼,咱們先從index入口文件進行分析,入口文件在src/store.js文件中react

export default {
 // 主要代碼,狀態存儲類
  Store,
  // 插件安裝
  install,
  // 版本
  version: '__VERSION__',
  mapState,
  mapMutations,
  mapGetters,
  mapActions,
  createNamespacedHelpers
}

一個一個來看Store是vuex提供的狀態存儲類,一般咱們使用Vuex就是經過建立Store的實例,
install方法是配合Vue.use方法進行使用,install方法是用來編寫Vue插件的通用公式,先來看一下代碼es6

export function install (_Vue) {
  if (Vue && _Vue === Vue) {
    if (process.env.NODE_ENV !== 'production') {
      console.error(
        '[vuex] already installed. Vue.use(Vuex) should be called only once.'
      )
    }
    return
  }
  Vue = _Vue
  applyMixin(Vue)
}

這個方法的做用是什麼呢:當window上有Vue對象的時候,就會手動編寫install方法,而且傳入Vue的使用。
在install中法中有applyMixin這個函數,這個函數來自雲src/mixin.js,下面是mixin.js的代碼面試

export default function (Vue) {
// 判斷VUe的版本
  const version = Number(Vue.version.split('.')[0])
// 若是vue的版本大於2,那麼beforeCreate以前vuex進行初始化
  if (version >= 2) {
    Vue.mixin({ beforeCreate: vuexInit })
  } else {
    // override init and inject vuex init procedure
    // for 1.x backwards compatibility.
    // 兼容vue 1的版本
    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.
   */

// 給vue的實例註冊一個$store的屬性,相似我們使用vue.$route
  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的聲明週期中進行vuex的初始化,而且對vue的各類版本進行了兼容。vuexInit就是對vuex的初始化。爲何咱們能在使用vue.$store這種方法呢,由於在vuexInit中爲vue的實例添加了$store屬性
Store構造函數
先看constructor構造函數vuex

constructor (options = {}) {
    // Auto install if it is not done yet and `window` has `Vue`.
    // To allow users to avoid auto-installation in some cases,
    // this code should be placed here. See #731
    // 判斷window.vue是否存在,若是不存在那麼就安裝
    if (!Vue && typeof window !== 'undefined' && window.Vue) {
      install(window.Vue)
    }
// 這個是在開發過程當中的一些環節判斷,vuex要求在建立vuex 
// store實例以前必須先使用這個方法Vue.use(Vuex),而且判斷promise是否可使用
    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.`)
    }
    // 提取參數
    const {
      plugins = [],
      strict = false
    } = options

    let {
      state = {}
    } = options
    if (typeof state === 'function') {
      state = state() || {}
    }

    // store internal state
    // 初始化store內部狀態,Obejct.create(null)是ES5的一種方法,Object.create(
    // object)object是你要繼承的對象,若是是null,那麼就是建立一個純淨的對象
    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
    // 定義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
    // 嚴格模式
    this.strict = strict

    // init root module.
    // this also recursively registers all sub-modules
    // and collects all module getters inside this._wrappedGetters
    // 初始化根模塊,遞歸註冊全部的子模塊,收集getters
    installModule(this, state, [], this._modules.root)

    // initialize the store vm, which is responsible for the reactivity
    // (also registers _wrappedGetters as computed properties)
    // 重置vm狀態,同時將getters轉換爲computed計算屬性
    resetStoreVM(this, state)

    // apply plugins
    // 執行每一個插件裏邊的函數
    plugins.forEach(plugin => plugin(this))

    if (Vue.config.devtools) {
      devtoolPlugin(this)
    }
  }

由於vuex是由es6進行編寫的,我真以爲es6的模塊簡直是神器,我之前就納悶了像Echarts這種8萬行的庫,他們是怎麼寫出來的,上次還看到一個應聘百度的問Echarts的源碼沒有。可怕!後來才知道模塊化。現在es6引入了import這種模塊化語句,讓咱們編寫龐大的代碼變得更加簡單了。json

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

這段代碼檢測window.Vue是否存在,若是不存在那麼就調用install方法promise

assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`)

這行代碼用於確保在咱們實例化 Store以前,vue已經存在。瀏覽器

assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`)

這一行代碼用於檢測是否支持Promise,由於vuex中使用了Promise,Promise是es6的語法,可是有的瀏覽器並不支持es6因此咱們須要在package.json中加入babel-polyfill用來支持es6
下面來看接下來的代碼babel

const {
      plugins = [],
      strict = false
    } = options

這利用了es6的解構賦值拿到options中的plugins。es6的解構賦值在個人《前端面試之ES6》中講過,大體就是[a,b,c] = [1,2,3] 等同於a=1,b=2,c=3。後面一句的代碼相似取到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()

這裏主要用於建立一些內部的屬性,爲何要加_這是用於辨別屬性,_就表示內部屬性,咱們在外部調用這些屬性的時候,就須要當心。

this._committing 標誌一個提交狀態
this._actions 用來存儲用戶定義的全部actions
this._actionSubscribers 
this._mutations 用來存儲用戶定義全部的 mutatins
this._wrappedGetters 用來存儲用戶定義的全部 getters 
this._modules 
this._subscribers 用來存儲全部對 mutation 變化的訂閱者
this._watcherVM 是一個 Vue 對象的實例,主要是利用 Vue 實例方法 $watch 來觀測變化的
commit和dispatch方法在事後再講

installModule(this, state, [], options)
是將咱們的options傳入的各類屬性模塊註冊和安裝
resetStoreVM(this, state)
resetStoreVM 方法是初始化 store._vm,觀測 state 和 getters 的變化;最後是應用傳入的插件
下面是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.forEachAction((action, key) => {
    const type = action.root ? key : namespace + key
    const handler = action.handler || action
    registerAction(store, type, handler, local)
  })

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

這個函數的主要做用是什麼呢:初始化組件樹根組件、註冊全部子組件,並將全部的getters存儲到this._wrapperdGetters屬性中
這個函數接受5個函數,store, rootState, path, module, hot。咱們來看看他們分別表明什麼:
store: 表示當前Store實例
rootState: 表示根state,
path: 其餘的參數的含義都比較好理解,那麼這個path是如何理解的呢。咱們能夠將一個store實例當作module的集合。每個集合也是store的實例。那麼path就能夠想象成一種層級關係,當你有了rootState和path後,就能夠在Path路徑中找到local State。而後每次getters或者setters改變的就是localState
module:表示當前安裝的模塊
hot:當動態改變modules或者熱更新的時候爲true

const isRoot = !path.length
 const namespace = store._modules.getNamespace(path)

用於經過Path的長度來判斷是否爲根,下面那一句是用與註冊命名空間

module.forEachMutation((mutation, key) => {
    const namespacedType = namespace + key
    registerMutation(store, namespacedType, mutation, local)
  })

這句是將mutation註冊到模塊上,後面幾句是一樣的效果,就不作一一的解釋了

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

這兒是經過遍歷Modules,遞歸調用installModule安裝模塊。
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)
})

}
這段用於判斷若是不是根而且Hot爲false的狀況,將要執行的俄操做,這兒有一個函數getNestedState (state, path),函數的內容以下:

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

這個函數的意思拿到該module父級的state,拿到其所在的moduleName,而後調用store._withCommit()方法

store._withCommit(() => {
      Vue.set(parentState, moduleName, module.state)
})

把當前模塊的state添加到parentState,咱們使用了_withCommit()方法,_withCommit的方法咱們留到commit方法再講。

相關文章
相關標籤/搜索