vuex源碼閱讀分析

這幾天忙啊,有絕地求生要上分,英雄聯盟新賽季須要上分,就懶着什麼也沒寫,很慚愧。這個vuex,vue-router,vue的源碼我半個月前就看的差很少了,可是懶,哈哈。
下面是vuex的源碼分析
在分析源碼的時候咱們能夠寫幾個例子來進行了解,必定不要閉門造車,多寫幾個例子,也就明白了
在vuex源碼中選擇了example/counter這個文件做爲例子來進行理解
counter/store.js是vuex的核心文件,這個例子比較簡單,若是比較複雜咱們能夠採起分模塊來讓代碼結構更加清楚,如何分模塊請在vuex的官網中看如何使用。
咱們來看看store.js的代碼:vue

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是怎樣的一個構造函數(由於代碼太多,我把一些判斷進行省略,讓你們看的更清楚)vue-router

Stor構造函數

src/store.jsvuex

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進行綁定,咱們來看看app

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(),下面是this

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,spa

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。

由於文章寫太長了,估計沒人看,因此本文對代碼進行了簡化,有些地方沒寫出來,其餘都是比較簡單的地方。有興趣的能夠本身去看一下,最後補一張圖,結合起來看源碼更能事半功倍。這張圖要我本身經過源碼畫出來,再給我看幾天我也是畫不出來的,因此還須要努力啊
vuex原理圖

相關文章
相關標籤/搜索