Vuex源碼解析

寫在前面

因為對Vue.js很感興趣花鹅,而且平時工作的技術棧也是Vue.js,這幾個月花了些時間研究學習了一下Vue.js源碼,并做了總結與輸出。

文章的原地址:https://github.com/answershuto/learnVue卫枝。

在學習過程中,為Vue加上了中文的注釋https://github.com/answershuto/learnVue/tree/master/vue-src以及Vuex的注釋https://github.com/answershuto/learnVue/tree/master/vuex-src讹挎,希望可以對其他想學習源碼的小伙伴有所幫助校赤。

可能會有理解存在偏差的地方,歡迎提issue指出筒溃,共同學習马篮,共同進步。

Vuex

我們在使用Vue.js開發(fā)復雜的應用時铡羡,經常會遇到多個組件共享同一個狀態(tài)积蔚,亦或是多個組件會去更新同一個狀態(tài)意鲸,在應用代碼量較少的時候烦周,我們可以組件間通信去維護修改數(shù)據(jù)尽爆,或者是通過事件總線來進行數(shù)據(jù)的傳遞以及修改。但是當應用逐漸龐大以后读慎,代碼就會變得難以維護漱贱,從父組件開始通過prop傳遞多層嵌套的數(shù)據(jù)由于層級過深而顯得異常脆弱,而事件總線也會因為組件的增多夭委、代碼量的增大而顯得交互錯綜復雜幅狮,難以捋清其中的傳遞關系。

那么為什么我們不能將數(shù)據(jù)層與組件層抽離開來呢株灸?把數(shù)據(jù)層放到全局形成一個單一的Store崇摄,組件層變得更薄,專門用來進行數(shù)據(jù)的展示及操作慌烧。所有數(shù)據(jù)的變更都需要經過全局的Store來進行逐抑,形成一個單向數(shù)據(jù)流,使數(shù)據(jù)變化變得“可預測”屹蚊。

Vuex是一個專門為Vue.js框架設計的厕氨、用于對Vue.js應用程序進行狀態(tài)管理的庫,它借鑒了Flux汹粤、redux的基本思想命斧,將共享的數(shù)據(jù)抽離到全局,以一個單例存放嘱兼,同時利用Vue.js的響應式機制來進行高效的狀態(tài)管理與更新国葬。正是因為Vuex使用了Vue.js內部的“響應式機制”,所以Vuex是一個專門為Vue.js設計并與之高度契合的框架(優(yōu)點是更加簡潔高效芹壕,缺點是只能跟Vue.js搭配使用)胃惜。具體使用方法及API可以參考Vuex的官網(wǎng)

先來看一下這張Vuex的數(shù)據(jù)流程圖哪雕,熟悉Vuex使用的同學應該已經有所了解船殉。

Vuex實現(xiàn)了一個單向數(shù)據(jù)流,在全局擁有一個State存放數(shù)據(jù)斯嚎,所有修改State的操作必須通過Mutation進行利虫,Mutation的同時提供了訂閱者模式供外部插件調用獲取State數(shù)據(jù)的更新。所有異步接口需要走Action堡僻,常見于調用后端接口異步獲取更新數(shù)據(jù)糠惫,而Action也是無法直接修改State的,還是需要通過Mutation來修改State的數(shù)據(jù)钉疫。最后硼讽,根據(jù)State的變化,渲染到視圖上牲阁。Vuex運行依賴Vue內部數(shù)據(jù)雙向綁定機制固阁,需要new一個Vue對象來實現(xiàn)“響應式化”壤躲,所以Vuex是一個專門為Vue.js設計的狀態(tài)管理庫。

安裝

使用過Vuex的朋友一定知道备燃,Vuex的安裝十分簡單碉克,只需要提供一個store,然后執(zhí)行下面兩句代碼即完成的Vuex的引入并齐。

Vue.use(Vuex);

/*將store放入Vue創(chuàng)建時的option中*/
new Vue({
    el: '#app',
    store
});

那么問題來了漏麦,Vuex是怎樣把store注入到Vue實例中去的呢?

Vue.js提供了Vue.use方法用來給Vue.js安裝插件况褪,內部通過調用插件的install方法(當插件是一個對象的時候)來進行插件的安裝撕贞。

我們來看一下Vuex的install實現(xiàn)。

/*暴露給外部的插件install方法测垛,供Vue.use調用安裝插件*/
export function install (_Vue) {
  if (Vue) {
    /*避免重復安裝(Vue.use內部也會檢測一次是否重復安裝同一個插件)*/
    if (process.env.NODE_ENV !== 'production') {
      console.error(
        '[vuex] already installed. Vue.use(Vuex) should be called only once.'
      )
    }
    return
  }
  /*保存Vue麻掸,同時用于檢測是否重復安裝*/
  Vue = _Vue
  /*將vuexInit混淆進Vue的beforeCreate(Vue2.0)或_init方法(Vue1.0)*/
  applyMixin(Vue)
}

這段install代碼做了兩件事情,一件是防止Vuex被重復安裝赐纱,另一件是執(zhí)行applyMixin脊奋,目的是執(zhí)行vuexInit方法初始化Vuex。Vuex針對Vue1.0與2.0分別進行了不同的處理疙描,如果是Vue1.0诚隙,Vuex會將vuexInit方法放入Vue的_init方法中,而對于Vue2.0起胰,則會將vuexinit混淆進Vue的beforeCreacte鉤子中久又。來看一下vuexInit的代碼。

 /*Vuex的init鉤子效五,會存入每一個Vue實例等鉤子列表*/
  function vuexInit () {
    const options = this.$options
    // store injection
    if (options.store) {
      /*存在store其實代表的就是Root節(jié)點地消,直接執(zhí)行store(function時)或者使用store(非function)*/
      this.$store = typeof options.store === 'function'
        ? options.store()
        : options.store
    } else if (options.parent && options.parent.$store) {
      /*子組件直接從父組件中獲取$store,這樣就保證了所有組件都公用了全局的同一份store*/
      this.$store = options.parent.$store
    }
  }

vuexInit會嘗試從options中獲取store畏妖,如果當前組件是根組件(Root節(jié)點)脉执,則options中會存在store,直接獲取賦值給$store即可戒劫。如果當前組件非根組件半夷,則通過options中的parent獲取父組件的$store引用。這樣一來迅细,所有的組件都獲取到了同一份內存地址的Store實例巫橄,于是我們可以在每一個組件中通過this.$store愉快地訪問全局的Store實例了。

那么茵典,什么是Store實例湘换?

Store

我們傳入到根組件到store,就是Store實例,用Vuex提供到Store方法構造彩倚。

export default new Vuex.Store({
    strict: true,
    modules: {
        moduleA,
        moduleB
    }
});

我們來看一下Store的實現(xiàn)筹我。首先是構造函數(shù)。

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
    /*
      在瀏覽器環(huán)境下署恍,如果插件還未安裝(!Vue即判斷是否未安裝),則它會自動安裝蜻直。
      它允許用戶在某些情況下避免自動安裝盯质。
    */
    if (!Vue && typeof window !== 'undefined' && window.Vue) {
      install(window.Vue)
    }

    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 {
      /*一個數(shù)組,包含應用在 store 上的插件方法概而。這些插件直接接收 store 作為唯一參數(shù)呼巷,可以監(jiān)聽 mutation(用于外部地數(shù)據(jù)持久化、記錄或調試)或者提交 mutation (用于內部數(shù)據(jù)赎瑰,例如 websocket 或 某些觀察者)*/
      plugins = [],
      /*使 Vuex store 進入嚴格模式王悍,在嚴格模式下,任何 mutation 處理函數(shù)以外修改 Vuex state 都會拋出錯誤餐曼。*/
      strict = false
    } = options

    /*從option中取出state压储,如果state是function則執(zhí)行,最終得到一個對象*/
    let {
      state = {}
    } = options
    if (typeof state === 'function') {
      state = state()
    }

    // store internal state
    /* 用來判斷嚴格模式下是否是用mutation修改state的 */
    this._committing = false
    /* 存放action */
    this._actions = Object.create(null)
    /* 存放mutation */
    this._mutations = Object.create(null)
    /* 存放getter */
    this._wrappedGetters = Object.create(null)
    /* module收集器 */
    this._modules = new ModuleCollection(options)
    /* 根據(jù)namespace存放module */
    this._modulesNamespaceMap = Object.create(null)
    /* 存放訂閱者 */
    this._subscribers = []
    /* 用以實現(xiàn)Watch的Vue實例 */
    this._watcherVM = new Vue()

    // bind commit and dispatch to self
    /*將dispatch與commit調用的this綁定為store對象本身源譬,否則在組件內部this.dispatch時的this會指向組件的vm*/
    const store = this
    const { dispatch, commit } = this
    /* 為dispatch與commit綁定this(Store實例本身) */
    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
    /*嚴格模式(使 Vuex store 進入嚴格模式集惋,在嚴格模式下,任何 mutation 處理函數(shù)以外修改 Vuex state 都會拋出錯誤)*/
    this.strict = strict

    // init root module.
    // this also recursively registers all sub-modules
    // and collects all module getters inside this._wrappedGetters
    /*初始化根module踩娘,這也同時遞歸注冊了所有子modle刮刑,收集所有module的getter到_wrappedGetters中去,this._modules.root代表根module才獨有保存的Module對象*/
    installModule(this, state, [], this._modules.root)

    // initialize the store vm, which is responsible for the reactivity
    // (also registers _wrappedGetters as computed properties)
    /* 通過vm重設store养渴,新建Vue對象使用Vue內部的響應式實現(xiàn)注冊state以及computed */
    resetStoreVM(this, state)

    // apply plugins
    /* 調用插件 */
    plugins.forEach(plugin => plugin(this))

    /* devtool插件 */
    if (Vue.config.devtools) {
      devtoolPlugin(this)
    }
  }

Store的構造類除了初始化一些內部變量以外雷绢,主要執(zhí)行了installModule(初始化module)以及resetStoreVM(通過VM使store“響應式”)。

installModule

installModule的作用主要是用為module加上namespace名字空間(如果有)后理卑,注冊mutation翘紊、action以及getter,同時遞歸安裝所有子module藐唠。

/*初始化module*/
function installModule (store, rootState, path, module, hot) {
  /* 是否是根module */
  const isRoot = !path.length
  /* 獲取module的namespace */
  const namespace = store._modules.getNamespace(path)

  // register in namespace map
  /* 如果有namespace則在_modulesNamespaceMap中注冊 */
  if (module.namespaced) {
    store._modulesNamespaceMap[namespace] = module
  }

  // set state
  if (!isRoot && !hot) {
    /* 獲取父級的state */
    const parentState = getNestedState(rootState, path.slice(0, -1))
    /* module的name */
    const moduleName = path[path.length - 1]
    store.`_withCommit`(() => {
      /* 將子module設置稱響應式的 */
      Vue.set(parentState, moduleName, module.state)
    })
  }

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

  /* 遞歸安裝mudule */
  module.forEachChild((child, key) => {
    installModule(store, rootState, path.concat(key), child, hot)
  })
}

resetStoreVM

在說resetStoreVM之前霞溪,先來看一個小demo。

let globalData = {
    d: 'hello world'
};
new Vue({
    data () {
        return {
            $$state: {
                globalData
            }
        }
    }
});

/* modify */
setTimeout(() => {
    globalData.d = 'hi~';
}, 1000);

Vue.prototype.globalData = globalData;

/* 任意模板中 */
<div>{{globalData.d}}</div>

上述代碼在全局有一個globalData中捆,它被傳入一個Vue對象的data中鸯匹,之后在任意Vue模板中對該變量進行展示,因為此時globalData已經在Vue的prototype上了所以直接通過this.prototype訪問泄伪,也就是在模板中的{{prototype.d}}殴蓬。此時,setTimeout在1s之后將globalData.d進行修改,我們發(fā)現(xiàn)模板中的globalData.d發(fā)生了變化染厅。其實上述部分就是Vuex依賴Vue核心實現(xiàn)數(shù)據(jù)的“響應式化”痘绎。

不熟悉Vue.js響應式原理的同學可以通過筆者另一篇文章響應式原理了解Vue.js是如何進行數(shù)據(jù)雙向綁定的。

接著來看代碼肖粮。

/* 通過vm重設store孤页,新建Vue對象使用Vue內部的響應式實現(xiàn)注冊state以及computed */
function resetStoreVM (store, state, hot) {
  /* 存放之前的vm對象 */
  const oldVm = store._vm 

  // bind store public getters
  store.getters = {}
  const wrappedGetters = store._wrappedGetters
  const computed = {}

  /* 通過Object.defineProperty為每一個getter方法設置get方法,比如獲取this.$store.getters.test的時候獲取的是store._vm.test涩馆,也就是Vue對象的computed屬性 */
  forEachValue(wrappedGetters, (fn, key) => {
    // use computed to leverage its lazy-caching mechanism
    computed[key] = () => fn(store)
    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的目的是在new一個Vue實例的過程中不會報出一切警告 */
  Vue.config.silent = true
  /*  這里new了一個Vue對象行施,運用Vue內部的響應式實現(xiàn)注冊state以及computed*/
  store._vm = new Vue({
    data: {
      $$state: state
    },
    computed
  })
  Vue.config.silent = silent

  // enable strict mode for new vm
  /* 使能嚴格模式,保證修改store只能通過mutation */
  if (store.strict) {
    enableStrictMode(store)
  }

  if (oldVm) {
    /* 解除舊vm的state的引用魂那,以及銷毀舊的Vue對象 */
    if (hot) {
      // dispatch changes in all subscribed watchers
      // to force getter re-evaluation for hot reloading.
      store._withCommit(() => {
        oldVm._data.$$state = null
      })
    }
    Vue.nextTick(() => oldVm.$destroy())
  }
}

resetStoreVM首先會遍歷wrappedGetters蛾号,使用Object.defineProperty方法為每一個getter綁定上get方法,這樣我們就可以在組件里訪問this.$store.getter.test就等同于訪問store._vm.test涯雅。

forEachValue(wrappedGetters, (fn, key) => {
  // use computed to leverage its lazy-caching mechanism
  computed[key] = () => fn(store)
  Object.defineProperty(store.getters, key, {
    get: () => store._vm[key],
    enumerable: true // for local getters
  })
})

之后Vuex采用了new一個Vue對象來實現(xiàn)數(shù)據(jù)的“響應式化”鲜结,運用Vue.js內部提供的數(shù)據(jù)雙向綁定功能來實現(xiàn)store的數(shù)據(jù)與視圖的同步更新。

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

這時候我們訪問store._vm.test也就訪問了Vue實例中的屬性活逆。

這兩步執(zhí)行完以后精刷,我們就可以通過this.$store.getter.test訪問vm中的test屬性了。

嚴格模式

Vuex的Store構造類的option有一個strict的參數(shù)蔗候,可以控制Vuex執(zhí)行嚴格模式贬养,嚴格模式下,所有修改state的操作必須通過mutation實現(xiàn)琴庵,否則會拋出錯誤误算。

/* 使能嚴格模式 */
function enableStrictMode (store) {
  store._vm.$watch(function () { return this._data.$$state }, () => {
    if (process.env.NODE_ENV !== 'production') {
      /* 檢測store中的_committing的值,如果是true代表不是通過mutation的方法修改的 */
      assert(store._committing, `Do not mutate vuex store state outside mutation handlers.`)
    }
  }, { deep: true, sync: true })
}

首先迷殿,在嚴格模式下儿礼,Vuex會利用vm的$watch方法來觀察$$state,也就是Store的state庆寺,在它被修改的時候進入回調蚊夫。我們發(fā)現(xiàn),回調中只有一句話懦尝,用assert斷言來檢測store._committing知纷,當store._committing為false的時候會觸發(fā)斷言,拋出異常陵霉。

我們發(fā)現(xiàn)琅轧,Store的commit方法中,執(zhí)行mutation的語句是這樣的踊挠。

this._withCommit(() => {
  entry.forEach(function commitIterator (handler) {
    handler(payload)
  })
})

再來看看_withCommit的實現(xiàn)乍桂。

_withCommit (fn) {
  /* 調用withCommit修改state的值時會將store的committing值置為true,內部會有斷言檢查該值,在嚴格模式下只允許使用mutation來修改store中的值睹酌,而不允許直接修改store的數(shù)值 */
  const committing = this._committing
  this._committing = true
  fn()
  this._committing = committing
}

我們發(fā)現(xiàn)权谁,通過commit(mutation)修改state數(shù)據(jù)的時候,會再調用mutation方法之前將committing置為true憋沿,接下來再通過mutation函數(shù)修改state中的數(shù)據(jù)旺芽,這時候觸發(fā)$watch中的回調斷言committing是不會拋出異常的(此時committing為true)。而當我們直接修改state的數(shù)據(jù)時辐啄,觸發(fā)$watch的回調執(zhí)行斷言采章,這時committing為false,則會拋出異常则披。這就是Vuex的嚴格模式的實現(xiàn)共缕。

接下來我們來看看Store提供的一些API洗出。

commit(mutation

/* 調用mutation的commit方法 */
commit (_type, _payload, _options) {
  // check object-style commit
  /* 校驗參數(shù) */
  const {
    type,
    payload,
    options
  } = unifyObjectStyle(_type, _payload, _options)

  const mutation = { type, payload }
  /* 取出type對應的mutation的方法 */
  const entry = this._mutations[type]
  if (!entry) {
    if (process.env.NODE_ENV !== 'production') {
      console.error(`[vuex] unknown mutation type: ${type}`)
    }
    return
  }
  /* 執(zhí)行mutation中的所有方法 */
  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'
    )
  }
}

commit方法會根據(jù)type找到并調用_mutations中的所有type對應的mutation方法士复,所以當沒有namespace的時候,commit方法會觸發(fā)所有module中的mutation方法翩活。再執(zhí)行完所有的mutation之后會執(zhí)行_subscribers中的所有訂閱者阱洪。我們來看一下_subscribers是什么。

Store給外部提供了一個subscribe方法菠镇,用以注冊一個訂閱函數(shù)冗荸,會push到Store實例的_subscribers中,同時返回一個從_subscribers中注銷該訂閱者的方法利耍。

/* 注冊一個訂閱函數(shù)蚌本,返回取消訂閱的函數(shù) */
subscribe (fn) {
  const subs = this._subscribers
  if (subs.indexOf(fn) < 0) {
    subs.push(fn)
  }
  return () => {
    const i = subs.indexOf(fn)
    if (i > -1) {
      subs.splice(i, 1)
    }
  }
}

在commit結束以后則會調用這些_subscribers中的訂閱者,這個訂閱者模式提供給外部一個監(jiān)視state變化的可能隘梨。state通過mutation改變時程癌,可以有效補獲這些變化。

dispatch(action

來看一下dispatch的實現(xiàn)轴猎。

/* 調用action的dispatch方法 */
dispatch (_type, _payload) {
  // check object-style dispatch
  const {
    type,
    payload
  } = unifyObjectStyle(_type, _payload)

  /* actions中取出type對應的ation */
  const entry = this._actions[type]
  if (!entry) {
    if (process.env.NODE_ENV !== 'production') {
      console.error(`[vuex] unknown action type: ${type}`)
    }
    return
  }

  /* 是數(shù)組則包裝Promise形成一個新的Promise嵌莉,只有一個則直接返回第0個 */
  return entry.length > 1
    ? Promise.all(entry.map(handler => handler(payload)))
    : entry[0](payload)
}

以及registerAction時候做的事情。

/* 遍歷注冊action */
function registerAction (store, type, handler, local) {
  /* 取出type對應的action */
  const entry = store._actions[type] || (store._actions[type] = [])
  entry.push(function wrappedActionHandler (payload, cb) {
    let res = handler.call(store, {
      dispatch: local.dispatch,
      commit: local.commit,
      getters: local.getters,
      state: local.state,
      rootGetters: store.getters,
      rootState: store.state
    }, payload, cb)
    /* 判斷是否是Promise */
    if (!isPromise(res)) {
      /* 不是Promise對象的時候轉化稱Promise對象 */
      res = Promise.resolve(res)
    }
    if (store._devtoolHook) {
      /* 存在devtool插件的時候觸發(fā)vuex的error給devtool */
      return res.catch(err => {
        store._devtoolHook.emit('vuex:error', err)
        throw err
      })
    } else {
      return res
    }
  })
}

因為registerAction的時候將push進_actions的action進行了一層封裝(wrappedActionHandler)捻脖,所以我們在進行dispatch的第一個參數(shù)中獲取state锐峭、commit等方法。之后可婶,執(zhí)行結果res會被進行判斷是否是Promise沿癞,不是則會進行一層封裝,將其轉化成Promise對象矛渴。dispatch時則從_actions中取出抛寝,只有一個的時候直接返回,否則用Promise.all處理再返回。

watch

/* 觀察一個getter方法 */
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)
}

熟悉Vue的朋友應該很熟悉watch這個方法盗舰。這里采用了比較巧妙的設計晶府,_watcherVM是一個Vue的實例,所以watch就可以直接采用了Vue內部的watch特性提供了一種觀察數(shù)據(jù)getter變動的方法钻趋。

registerModule

/* 注冊一個動態(tài)module川陆,當業(yè)務進行異步加載的時候,可以通過該接口進行注冊動態(tài)module */
registerModule (path, rawModule) {
  /* 轉化稱Array */
  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)
  /*初始化module*/
  installModule(this, this.state, path, this._modules.get(path))
  // reset store to update getters...
  /* 通過vm重設store蛮位,新建Vue對象使用Vue內部的響應式實現(xiàn)注冊state以及computed */
  resetStoreVM(this, this.state)
}

registerModule用以注冊一個動態(tài)模塊较沪,也就是在store創(chuàng)建以后再注冊模塊的時候用該接口。內部實現(xiàn)實際上也只有installModule與resetStoreVM兩個步驟失仁,前面已經講過尸曼,這里不再累述。

unregisterModule

 /* 注銷一個動態(tài)module */
unregisterModule (path) {
  /* 轉化稱Array */
  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(() => {
    /* 獲取父級的state */
    const parentState = getNestedState(this.state, path.slice(0, -1))
    /* 從父級中刪除 */
    Vue.delete(parentState, path[path.length - 1])
  })
  /* 重制store */
  resetStore(this)
}

同樣萄焦,與registerModule對應的方法unregisterModule控轿,動態(tài)注銷模塊。實現(xiàn)方法是先從state中刪除模塊拂封,然后用resetStore來重制store茬射。

resetStore

/* 重制store */
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)
}

這里的resetStore其實也就是將store中的_actions等進行初始化以后,重新執(zhí)行installModule與resetStoreVM來初始化module以及用Vue特性使其“響應式化”冒签,這跟構造函數(shù)中的是一致的在抛。

插件

Vue提供了一個非常好用的插件Vue.js devtools

/* 從window對象的__VUE_DEVTOOLS_GLOBAL_HOOK__中獲取devtool插件 */
const devtoolHook =
  typeof window !== 'undefined' &&
  window.__VUE_DEVTOOLS_GLOBAL_HOOK__

export default function devtoolPlugin (store) {
  if (!devtoolHook) return

  /* devtoll插件實例存儲在store的_devtoolHook上 */
  store._devtoolHook = devtoolHook

  /* 出發(fā)vuex的初始化事件,并將store的引用地址傳給deltool插件萧恕,使插件獲取store的實例 */
  devtoolHook.emit('vuex:init', store)

  /* 監(jiān)聽travel-to-state事件 */
  devtoolHook.on('vuex:travel-to-state', targetState => {
    /* 重制state */
    store.replaceState(targetState)
  })

  /* 訂閱store的變化 */
  store.subscribe((mutation, state) => {
    devtoolHook.emit('vuex:mutation', mutation, state)
  })
}

如果已經安裝了該插件刚梭,則會在windows對象上暴露一個VUE_DEVTOOLS_GLOBAL_HOOK。devtoolHook用在初始化的時候會觸發(fā)“vuex:init”事件通知插件票唆,然后通過on方法監(jiān)聽“vuex:travel-to-state”事件來重置state朴读。最后通過Store的subscribe方法來添加一個訂閱者,在觸發(fā)commit方法修改mutation數(shù)據(jù)以后惰说,該訂閱者會被通知磨德,從而觸發(fā)“vuex:mutation”事件。

最后

Vuex是一個非常優(yōu)秀的庫吆视,代碼量不多且結構清晰典挑,非常適合研究學習其內部實現(xiàn)。最近的一系列源碼閱讀也使我自己受益匪淺啦吧,寫這篇文章也希望可以幫助到更多想要學習探索Vuex內部實現(xiàn)原理的同學您觉。

關于

作者:染陌

Email:answershuto@gmail.com or answershuto@126.com

Github: https://github.com/answershuto

Blog:http://answershuto.github.io/

知乎主頁:https://www.zhihu.com/people/cao-yang-49/activities

知乎專欄:https://zhuanlan.zhihu.com/ranmo

掘金: https://juejin.im/user/58f87ae844d9040069ca7507

osChina:https://my.oschina.net/u/3161824/blog

轉載請注明出處,謝謝授滓。

歡迎關注我的公眾號

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末琳水,一起剝皮案震驚了整個濱河市肆糕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌在孝,老刑警劉巖诚啃,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異私沮,居然都是意外死亡始赎,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門仔燕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來造垛,“玉大人,你說我怎么就攤上這事晰搀∥辶桑” “怎么了?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵外恕,是天一觀的道長杆逗。 經常有香客問我,道長吁讨,這世上最難降的妖魔是什么髓迎? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任峦朗,我火速辦了婚禮建丧,結果婚禮上,老公的妹妹穿的比我還像新娘波势。我一直安慰自己翎朱,他們只是感情好,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布尺铣。 她就那樣靜靜地躺著拴曲,像睡著了一般。 火紅的嫁衣襯著肌膚如雪凛忿。 梳的紋絲不亂的頭發(fā)上澈灼,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天,我揣著相機與錄音店溢,去河邊找鬼叁熔。 笑死,一個胖子當著我的面吹牛床牧,可吹牛的內容都是我干的荣回。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼戈咳,長吁一口氣:“原來是場噩夢啊……” “哼心软!你這毒婦竟也來了壕吹?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤删铃,失蹤者是張志新(化名)和其女友劉穎耳贬,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體猎唁,經...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡效拭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了胖秒。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片缎患。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖阎肝,靈堂內的尸體忽然破棺而出挤渔,到底是詐尸還是另有隱情,我是刑警寧澤风题,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布判导,位于F島的核電站,受9級特大地震影響沛硅,放射性物質發(fā)生泄漏眼刃。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一摇肌、第九天 我趴在偏房一處隱蔽的房頂上張望擂红。 院中可真熱鬧,春花似錦围小、人聲如沸昵骤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽变秦。三九已至,卻和暖如春框舔,著一層夾襖步出監(jiān)牢的瞬間蹦玫,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工刘绣, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留樱溉,地道東北人。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓额港,卻偏偏與公主長得像饺窿,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子移斩,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內容

  • 上一章總結了 Vuex 的框架原理肚医,這一章我們將從 Vuex 的入口文件開始绢馍,分步驟閱讀和解析源碼。由于 Vuex...
    你的肖同學閱讀 1,785評論 3 16
  • 安裝 npm npm install vuex --save 在一個模塊化的打包系統(tǒng)中肠套,您必須顯式地通過Vue.u...
    蕭玄辭閱讀 2,934評論 0 7
  • Vuex是什么舰涌? Vuex 是一個專為 Vue.js應用程序開發(fā)的狀態(tài)管理模式。它采用集中式存儲管理應用的所有組件...
    蕭玄辭閱讀 3,114評論 0 6
  • Vuex 是一個專為 Vue.js 應用程序開發(fā)的狀態(tài)管理模式你稚。它采用集中式存儲管理應用的所有組件的狀態(tài)瓷耙,并以相應...
    白水螺絲閱讀 4,666評論 7 61
  • 這是我四年前在新浪寫的一篇舊文,今天翻看刁赖,覺得有點意思搁痛。有那么多一段時間狐史,一直泡在圖書館中舶替,讀民國十大才...
    一個影視文案的自白閱讀 804評論 1 4