學習vuex源碼

這一篇主要是講解vuex的大概實現,以及一些細節的說明。vue

vuex是如何實現的?

先從install方法看,安裝插件的方法實現比較簡單,調用applyMixin,最後執行的是這段邏輯Vue.mixin({ beforeCreate: vuexInit }),其實是在每一個組件建立時混入store實例。因此咱們能夠在每一個組件上獲取到store實例上的數據。react

if (version >= 2) {
Vue.mixin({ beforeCreate: vuexInit })
} else {
// override init and inject vuex init procedure
// for 1.x backwards compatibility.
const _init = Vue.prototype._init
Vue.prototype._init = function (options = {}) {
  options.init = options.init
    ? [vuexInit].concat(options.init)
    : vuexInit
  _init.call(this, options)
}
}

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
}
}
複製代碼
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)
}
複製代碼

接下來看看store這個類的代碼:vuex

在這個類上暴露了commitdispatch方法,而且綁定了調用上下文爲store的實例。dispatch支持異步更新數據是由於它內部的實現就是使用了promisepromise

// 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)
}
複製代碼

下面兩個方法是vuex的實現主要邏輯,installModule的具體邏輯不細說,從註釋上咱們能夠知道,這是一個初始化整個vuex配置生成對象的方法。根據配置進行遞歸執行。bash

// init root module.
// this also recursively registers all sub-modules
// and collects all module getters inside this._wrappedGetters
installModule(this, state, [], this._modules.root)

// initialize the store vm, which is responsible for the reactivity
// (also registers _wrappedGetters as computed properties)
resetStoreVM(this, state)
複製代碼

resetStoreVM方法比較重要,下面是該方法的詳細代碼:app

  1. vuex中的getter其實是使用vue計算屬性實現的,Object.defineProperty裏定義了getterget方法。
  2. store._vm實際就是vuexinstallModule生成的對象改形成響應式的數據,經過一個新的vue實例。
  3. 最後則是當咱們重置vuex的響應數據,須要銷燬舊的實例,回收內存。
function resetStoreVM (store, state, hot) {
  const oldVm = store._vm

  // bind store public getters
  store.getters = {}
  const wrappedGetters = store._wrappedGetters
  const computed = {}
  forEachValue(wrappedGetters, (fn, key) => {
    // use computed to leverage its lazy-caching mechanism
    // direct inline function use will lead to closure preserving oldVm.
    // using partial to return function with only arguments preserved in closure enviroment.
    computed[key] = partial(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
  store._vm = new Vue({
    data: {
      $$state: state
    },
    computed
  })
  Vue.config.silent = silent

  // enable strict mode for new vm
  if (store.strict) {
    enableStrictMode(store)
  }

  if (oldVm) {
    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())
  }
}
複製代碼

修改vuex的數據是如何響應到視圖?

  1. vuexstategetter實際上就是對應的store._vmvue實例)中datacomputed,所以getter所依賴的state最後是經過watcher管理的。
  2. 當咱們在組件中使用vuexvuex生成的實例對象在vue實例化過程當中被改形成響應式的數據,當咱們有多個頁面組件使用了vuex的數據,其實也是經過watcher管理,所以當咱們使用commitdispatch修改數據,最後觸發了setter去通知全部訂閱者(watcher)更新。

總結

vuex的設計其實並不複雜,簡單的來說,就是一個對象,經過內部的方法管理內部的屬性和讀取內部的屬性。異步

而實現的過程,則是經過一系列方法把咱們的配置生成一個root對象,而後利用vue實現內部數據的響應與依賴管理。而這裏比較核心的部分則是當數據發生變化時,如何響應到對應的視圖部分以及getter的依賴管理,這些邏輯的實現最後都是經過watcheride

相關文章
相關標籤/搜索