前言
通過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組件