淺析vuex狀態管理實現原理

前言

通過B站視頻和一些童鞋的文章結合Git源碼閱讀來理解vuex的實現原理

話不多說,我們直接上源碼

首先來看一下vuex的源碼目錄,衆所周知,主要工程一般都在 src 下,所以我們直接從這裏開始
在這裏插入圖片描述

  • module:模塊構造函數和模塊集合管理
  • plugins:插件,調試 dvtools 日誌記錄吧 logger
  • helpers:集成語法糖 mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers
  • index:入口文件,導出 store , install , 及以上 語法糖 ↑
  • mixin:混入,初始化 vuex , 並掛載在 Vue 根實例上
  • store:vuex 構造函數,實現功能的主體函數
  • utils:一些工具方法吧

好了,基本就是這些東西;通過官方文檔我們知道,每一個vue插件都需要有一個公開的install方法,vuex也不例外。我們一步步分析

src/index.js

import { Store, install } from './store'  // 導入並執行 install 初始化
import { mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers } from './helpers'
export default {
  Store,
  install,
  version: '__VERSION__',
  mapState,
  mapMutations,
  mapGetters,
  mapActions,
  createNamespacedHelpers
}

整個js文件裏沒有什麼多餘的東西,就是導入和導出,我們回頭想一下,使用第三方插件是不是都需要安裝啊,其實就是那句 Vue.use(插件) 這裏不過多解釋Vue.use() 具體做了哪些事兒,只需要知道,執行這句代碼,會安裝插件,並且執行插件的默認公開 install 方法。ok ,走進 install 初始化

src/store.js 523行 store.js 代碼量挺大,我就不一一羅列,用到哪裏,就截出對應代碼,好吧

export function install (_Vue) { // 導出 install 
  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 
  /* 
  vue 指當前跟實例  src/store.js 13行 constructor 中有一句
   if (!Vue && typeof window !== 'undefined' && window.Vue) {
      install(window.Vue) 
    }
  */
  applyMixin(Vue)
}

好,順藤摸瓜,我們走進 applyMixin 方法在 src/mixins.js

export default function (Vue) { // 傳入 vue 實例
  const version = Number(Vue.version.split('.')[0])
  if (version >= 2) {
    Vue.mixin({ beforeCreate: vuexInit }) // 生命週期創建前,混入掛載 vuex
  } else {
    const _init = Vue.prototype._init // 掛載在根實例上
    Vue.prototype._init = function (options = {}) {
      options.init = options.init
        ? [vuexInit].concat(options.init)
        : vuexInit
      _init.call(this, options)
    }
  }
  // 初始化 vuex 方法
  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
    }
  }
}

初始化過後,我們回過頭來看看 vuex 在日常開發中到底是怎麼使用的,如下代碼

import Vue from 'vue' // 導入 vue 實例
import Vuex from 'vuex' // 導入 狀態管理 vuex
Vue.use(Vuex) // 安裝初始化 vuex
const store = new Vuex.Store({ // 使用
    state:{},  // 狀態存儲的位置
    getters:{}, // 獲取狀態
    mutations:{}, // 定義同步修改state的地方,唯一的途徑
    actions:{}, // 異步修改state的地方,提交了一個mutaions
    modules:{} // 模塊分發
});

可以看到到在使用中主要就是如上五個知識點,也就是說 store 構造函數裏邊分別初始化和集成了對應的屬性和方法;

constructor 裏邊的初始化聲明

// 初始化一些參數
 this._committing = false                             // 是否在進行提交狀態標識
 this._actions = Object.create(null)                  // acitons 操作對象
 this._actionSubscribers = []                         // action 訂閱列表
 this._mutations = Object.create(null)                // mutations操作對象
 this._wrappedGetters = Object.create(null)           // 封裝後的 getters 集合對象
 this._modules = new ModuleCollection(options)        // vuex 支持 store 分模塊傳入,存儲分析後的 modules
 this._modulesNamespaceMap = Object.create(null)      // 模塊命名空間 map
 this._subscribers = []                               // 訂閱函數集合
 this._watcherVM = new Vue()                          // Vue 組件用於 watch 監視變化
 
// 替換 this 中的 dispatch, commit 方法,將 this 指向 store
 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)
 }
 // 是否使用嚴格模式
 this.strict = strict
 // 狀態樹
 const state = this._modules.root.state
 // 初始化模塊
 installModule(this, state, [], this._modules.root)
 // 拋開一切聲明,我們看向下邊這句代碼 ↓ 重置虛擬 vm 
 resetStoreVM(this, state) // 重點 , 重點, 重點, 整個 vuex 的功能實現方法
 // 依次載入插件
 plugins.forEach(plugin => plugin(this))
 // 調試工具
 if (Vue.config.devtools) {
   devtoolPlugin(this)
 }

resetStoreVM 重置 store 實例

function resetStoreVM (store, state, hot) {
  const oldVm = store._vm  // 複製舊的實例
  store.getters = {}   // 設置 getters 屬性 
  const wrappedGetters = store._wrappedGetters  // 儲存封裝後的 getters 集合對象
  const computed = {}
  // 遍歷 wrappedGetters 對象
  forEachValue(wrappedGetters, (fn, key) => {
   // 給 computed 對象添加 getter 對象屬性   
   // 這裏的 store.getters.xx 其實是訪問了 store._vm[xx] , (store._vm看下邊,是新建的vue實例 ) 
   // 給 computed 依次添加 getter 裏的屬性方法,方便 store._vm 新vue實例使用
    computed[key] = partial(fn, store)
    /*
     export function partial (fn, arg) {
		 return function () {
		    return fn(arg)
		 }
	 }
	*/
    // 爲每一個getters 對象重寫 get 方法 , 進行一個
    Object.defineProperty(store.getters, key, {
      get: () => store._vm[key],
      enumerable: true  // for local getters
    })
  })

  // 創建Vue實例來保存state,同時讓state變成響應式, vue 組件本身的響應式原理
  // store._vm._data.$$state = store.state
  store._vm = new Vue({
    data: {
      $$state: state
    },
    computed   // 計算屬性爲上邊 wrappedGetters(getter集合對象) 裏的每一個屬性方法 
  })

  // 只能通過commit方式更改狀態
  if (store.strict) {
    enableStrictMode(store)
  }
}

總結

Vuex的state狀態是響應式,是藉助vue的data是響應式,將state存入新建vue實例組件的data中;
Vuex的getters則是藉助vue的計算屬性computed實現數據實時監聽。

全部源碼解讀:參考

vuex中的store本質就是沒有template的vue組件

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章