vuex源碼閱讀

這幾天忙啊窥突,有絕地求生要上分,英雄聯(lián)盟新賽季需要上分硫嘶,就懶著什么也沒寫阻问,很慚愧。這個vuex,vue-router,vue的源碼我半個月前就看的差不多了沦疾,但是懶称近,哈哈。
下面是vuex的源碼分析
在分析源碼的時候我們可以寫幾個例子來進(jìn)行了解哮塞,一定不要閉門造車刨秆,多寫幾個例子,也就明白了
在vuex源碼中選擇了example/counter這個文件作為例子來進(jìn)行理解
counter/store.js是vuex的核心文件忆畅,這個例子比較簡單衡未,如果比較復(fù)雜我們可以采取分模塊來讓代碼結(jié)構(gòu)更加清楚,如何分模塊請在vuex的官網(wǎng)中看如何使用。
我們來看看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 應(yīng)用的核心就是 store(倉庫)如失。“store”基本上就是一個容器送粱,它包含著你的應(yīng)用中大部分的狀態(tài) 褪贵。在源碼中我們來看看Store是怎樣的一個構(gòu)造函數(shù)(因為代碼太多,我把一些判斷進(jìn)行省略葫督,讓大家看的更清楚)

Stor構(gòu)造函數(shù)

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表示應(yīng)用的插件橄镜,strict表示是否為嚴(yán)格模式偎快。然后對store
的一系列屬性進(jìn)行初始化,來看看這些屬性分別代表的是什么

  • _committing 表示一個提交狀態(tài)洽胶,在Store中能夠改變狀態(tài)只能是mutations,不能在外部隨意改變狀態(tài)
  • _actions用來收集所有action,在store的使用中經(jīng)常會涉及到的action
  • _actionSubscribers用來存儲所有對 action 變化的訂閱者
  • _mutations用來收集所有action,在store的使用中經(jīng)常會涉及到的mutation
  • _wappedGetters用來收集所有action,在store的使用中經(jīng)常會涉及到的getters
  • _modules 用來收集module,當(dāng)應(yīng)用足夠大的情況晒夹,store就會變得特別臃腫,所以才有了module姊氓。每個Module都是單獨的store,都有g(shù)etter丐怯、action、mutation
  • _subscribers 用來存儲所有對 mutation 變化的訂閱者
  • _watcherVM 一個Vue的實例翔横,主要是為了使用Vue的watch方法读跷,觀測數(shù)據(jù)的變化

后面獲取dispatch和commit然后將this.dipatch和commit進(jìn)行綁定,我們來看看

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有三個參數(shù)禾唁,_type表示mutation的類型效览,_playload表示額外的參數(shù),options表示一些配置荡短。unifyObjectStyle()這個函數(shù)就不列出來了丐枉,它的作用就是判斷傳入的_type是不是對象,如果是對象進(jìn)行響應(yīng)簡單的調(diào)整掘托。然后就是根據(jù)type來獲取相應(yīng)的mutation,如果不存在就報錯瘦锹,如果有就調(diào)用this._witchCommit。下面是_withCommit的具體實現(xiàn)

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

函數(shù)中多次提到了_committing闪盔,這個的作用我們在初始化Store的時候已經(jīng)提到了更改狀態(tài)只能通過mutatiton進(jìn)行更改弯院,其他方式都不行,通過檢測committing的值我們就可以查看在更改狀態(tài)的時候是否發(fā)生錯誤
繼續(xù)來看commit函數(shù)泪掀,this._withCommitting對獲取到的mutation進(jìn)行提交听绳,然后遍歷this._subscribers,調(diào)用回調(diào)函數(shù),并且將state傳入族淮。總體來說commit函數(shù)的作用就是提交Mutation,遍歷_subscribers調(diào)用回調(diào)函數(shù)
下面是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函數(shù)類似,首先獲取type對象的actions祝辣,然后遍歷action訂閱者進(jìn)行回調(diào)贴妻,對actions的長度進(jìn)行判斷,當(dāng)actions只有一個的時候進(jìn)行將payload傳入蝙斜,否則遍歷傳入?yún)?shù)名惩,返回一個Promise.
commit和dispatch函數(shù)談完,我們回到store.js中的Store構(gòu)造函數(shù)
this.strict表示是否開啟嚴(yán)格模式孕荠,嚴(yán)格模式下我們能夠觀測到state的變化情況娩鹉,線上環(huán)境記得關(guān)閉嚴(yán)格模式。
this._modules.root.state就是獲取根模塊的狀態(tài)稚伍,然后調(diào)用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個參數(shù)store表示當(dāng)前store實例弯予,rootState表示根組件狀態(tài),path表示組件路徑數(shù)組个曙,module表示當(dāng)前安裝的模塊锈嫩,hot 當(dāng)動態(tài)改變 modules 或者熱更新的時候為 true
首先進(jìn)通過計算組件路徑長度判斷是不是根組件,然后獲取對應(yīng)的命名空間垦搬,如果當(dāng)前模塊存在namepaced那么就在store上添加模塊
如果不是根組件和hot為false的情況呼寸,那么先通過getNestedState找到rootState的父組件的state,因為path表示組件路徑數(shù)組,那么最后一個元素就是該組件的路徑猴贰,最后通過_withComment()函數(shù)提交Mutation对雪,

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

這是Vue的部分,就是在parentState添加屬性moduleName米绕,并且值為module.state

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

這段代碼里邊有一個函數(shù)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
}

傳入的三個參數(shù)store, namspace, path,store表示Vuex.Store的實例,namspace表示命名空間瑟捣,path組件路徑。整體的意思就是本地化dispatch,commit,getter和state,意思就是新建一個對象上面有dispatch义郑,commit蝶柿,getter 和 state方法
那么installModule的那段代碼就是綁定方法在module.context和local上。繼續(xù)看后面的首先遍歷module的mutation非驮,通過mutation 和 key 首先獲取namespacedType,然后調(diào)用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)
  })
}

是不是感覺這個函數(shù)有些熟悉,store表示當(dāng)前Store實例劫笙,type表示類型芙扎,handler表示mutation執(zhí)行的回調(diào)函數(shù),local表示本地化后的一個變量填大。在最開始的時候我們就用到了這些東西戒洼,例如

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

首先獲取type對應(yīng)的mutation,然后向mutations數(shù)組push進(jìn)去包裝后的hander函數(shù)。

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

遍歷module允华,對module的每個子模塊都調(diào)用installModule()方法

講完installModule()方法圈浇,我們繼續(xù)往下看resetStoreVM()函數(shù)

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表示當(dāng)前Store實例磷蜀,state表示狀態(tài)召耘,hot就不用再說了。對store.getters進(jìn)行初始化褐隆,獲取store._wrappedGetters并且用計算屬性的方法存儲了store.getters,所以store.getters是Vue的計算屬性污它。使用defineProperty方法給store.getters添加屬性,當(dāng)我們使用store.getters[key]實際上獲取的是store._vm[key]庶弃。然后用一個Vue的實例data來存儲state 樹衫贬,這里使用slient=true,是為了取消提醒和警告歇攻,在這里將一下slient的作用固惯,silent表示靜默模式(網(wǎng)上找的定義),就是如果開啟就沒有提醒和警告掉伏。后面的代碼表示如果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函數(shù)中鸡捐,我們先前提到的this._watcherVM就起到了作用栈暇,在這里我們利用了this._watcherVM中$watch方法進(jìn)行了數(shù)據(jù)的檢測。watch 作用是響應(yīng)式的監(jiān)測一個 getter 方法的返回值箍镜,當(dāng)值改變時調(diào)用回調(diào)源祈。getter必須是一個函數(shù),如果返回的值發(fā)生了變化色迂,那么就調(diào)用cb回調(diào)函數(shù)香缺。
我們繼續(xù)來將

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

沒什么難度就是,注冊一個動態(tài)模塊歇僧,首先判斷path图张,將其轉(zhuǎn)換為數(shù)組,register()函數(shù)的作用就是初始化一個模塊诈悍,安裝模塊祸轮,重置Store._vm。

因為文章寫太長了侥钳,估計沒人看适袜,所以本文對代碼進(jìn)行了簡化,有些地方?jīng)]寫出來舷夺,其他都是比較簡單的地方苦酱。有興趣的可以自己去看一下售貌,最后補(bǔ)一張圖,結(jié)合起來看源碼更能事半功倍疫萤。這張圖要我自己通過源碼畫出來趁矾,再給我看幾天我也是畫不出來的,所以還需要努力啊


2761385260-5812e7755da76_articlex.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末给僵,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子详拙,更是在濱河造成了極大的恐慌帝际,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,576評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件饶辙,死亡現(xiàn)場離奇詭異蹲诀,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)弃揽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評論 3 399
  • 文/潘曉璐 我一進(jìn)店門脯爪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人矿微,你說我怎么就攤上這事痕慢。” “怎么了涌矢?”我有些...
    開封第一講書人閱讀 168,017評論 0 360
  • 文/不壞的土叔 我叫張陵掖举,是天一觀的道長。 經(jīng)常有香客問我娜庇,道長塔次,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,626評論 1 296
  • 正文 為了忘掉前任名秀,我火速辦了婚禮励负,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘匕得。我一直安慰自己继榆,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,625評論 6 397
  • 文/花漫 我一把揭開白布耗跛。 她就那樣靜靜地躺著裕照,像睡著了一般。 火紅的嫁衣襯著肌膚如雪调塌。 梳的紋絲不亂的頭發(fā)上晋南,一...
    開封第一講書人閱讀 52,255評論 1 308
  • 那天,我揣著相機(jī)與錄音羔砾,去河邊找鬼负间。 笑死偶妖,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的政溃。 我是一名探鬼主播趾访,決...
    沈念sama閱讀 40,825評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼董虱!你這毒婦竟也來了扼鞋?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,729評論 0 276
  • 序言:老撾萬榮一對情侶失蹤愤诱,失蹤者是張志新(化名)和其女友劉穎云头,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體淫半,經(jīng)...
    沈念sama閱讀 46,271評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡溃槐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,363評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了科吭。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片昏滴。...
    茶點故事閱讀 40,498評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖对人,靈堂內(nèi)的尸體忽然破棺而出谣殊,到底是詐尸還是另有隱情,我是刑警寧澤牺弄,帶...
    沈念sama閱讀 36,183評論 5 350
  • 正文 年R本政府宣布蟹倾,位于F島的核電站,受9級特大地震影響猖闪,放射性物質(zhì)發(fā)生泄漏鲜棠。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,867評論 3 333
  • 文/蒙蒙 一培慌、第九天 我趴在偏房一處隱蔽的房頂上張望豁陆。 院中可真熱鬧,春花似錦吵护、人聲如沸盒音。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽祥诽。三九已至,卻和暖如春瓮恭,著一層夾襖步出監(jiān)牢的瞬間雄坪,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評論 1 272
  • 我被黑心中介騙來泰國打工屯蹦, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留维哈,地道東北人绳姨。 一個月前我還...
    沈念sama閱讀 48,906評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像阔挠,于是被迫代替她去往敵國和親飘庄。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,507評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 上一章總結(jié)了 Vuex 的框架原理购撼,這一章我們將從 Vuex 的入口文件開始跪削,分步驟閱讀和解析源碼。由于 Vuex...
    你的肖同學(xué)閱讀 1,790評論 3 16
  • 安裝 npm npm install vuex --save 在一個模塊化的打包系統(tǒng)中迂求,您必須顯式地通過Vue.u...
    蕭玄辭閱讀 2,945評論 0 7
  • Vuex 是一個專為 Vue.js 應(yīng)用程序開發(fā)的狀態(tài)管理模式切揭。它采用集中式存儲管理應(yīng)用的所有組件的狀態(tài),并以相應(yīng)...
    白水螺絲閱讀 4,670評論 7 61
  • vuex 場景重現(xiàn):一個用戶在注冊頁面注冊了手機(jī)號碼锁摔,跳轉(zhuǎn)到登錄頁面也想拿到這個手機(jī)號碼,你可以通過vue的組件化...
    sunny519111閱讀 8,022評論 4 111
  • Vuex是什么哼审? Vuex 是一個專為 Vue.js應(yīng)用程序開發(fā)的狀態(tài)管理模式谐腰。它采用集中式存儲管理應(yīng)用的所有組件...
    蕭玄辭閱讀 3,122評論 0 6