Vuex源碼解析

来源:http://www.cnblogs.com/answershuto/archive/2017/10/30/7752970.html
-Advertisement-
Play Games

## 寫在前面 因為對Vue.js很感興趣,而且平時工作的技術棧也是Vue.js,這幾個月花了些時間研究學習了一下Vue.js源碼,並做了總結與輸出。 文章的原地址:[https://github.com/answershuto/learnVue](https://github.com/answer ...


## 寫在前面

因為對Vue.js很感興趣,而且平時工作的技術棧也是Vue.js,這幾個月花了些時間研究學習了一下Vue.js源碼,並做了總結與輸出。

文章的原地址:[https://github.com/answershuto/learnVue](https://github.com/answershuto/learnVue)。

在學習過程中,為Vue加上了中文的註釋[https://github.com/answershuto/learnVue/tree/master/vue-src](https://github.com/answershuto/learnVue/tree/master/vue-src)以及Vuex的註釋[https://github.com/answershuto/learnVue/tree/master/vuex-src](https://github.com/answershuto/learnVue/tree/master/vuex-src),希望可以對其他想學習源碼的小伙伴有所幫助。

可能會有理解存在偏差的地方,歡迎提issue指出,共同學習,共同進步。

## Vuex

我們在使用Vue.js開發複雜的應用時,經常會遇到多個組件共用同一個狀態,亦或是多個組件會去更新同一個狀態,在應用代碼量較少的時候,我們可以組件間通信去維護修改數據,或者是通過事件匯流排來進行數據的傳遞以及修改。但是當應用逐漸龐大以後,代碼就會變得難以維護,從父組件開始通過prop傳遞多層嵌套的數據由於層級過深而顯得異常脆弱,而事件匯流排也會因為組件的增多、代碼量的增大而顯得交互錯綜複雜,難以捋清其中的傳遞關係。

那麼為什麼我們不能將數據層與組件層抽離開來呢?把數據層放到全局形成一個單一的Store,組件層變得更薄,專門用來進行數據的展示及操作。所有數據的變更都需要經過全局的Store來進行,形成一個單向數據流,使數據變化變得“可預測”。

Vuex是一個專門為Vue.js框架設計的、用於對Vue.js應用程式進行狀態管理的庫,它借鑒了Flux、redux的基本思想,將共用的數據抽離到全局,以一個單例存放,同時利用Vue.js的響應式機制來進行高效的狀態管理與更新。正是因為Vuex使用了Vue.js內部的“響應式機制”,所以Vuex是一個專門為Vue.js設計並與之高度契合的框架(優點是更加簡潔高效,缺點是只能跟Vue.js搭配使用)。具體使用方法及API可以參考[Vuex的官網](https://vuex.vuejs.org/zh-cn/intro.html)。

先來看一下這張Vuex的數據流程圖,熟悉Vuex使用的同學應該已經有所瞭解。

![](https://user-gold-cdn.xitu.io/2017/10/30/0b020946dc1f988a5f05d3beebae02dd)

Vuex實現了一個單向數據流,在全局擁有一個State存放數據,所有修改State的操作必須通過Mutation進行,Mutation的同時提供了訂閱者模式供外部插件調用獲取State數據的更新。所有非同步介面需要走Action,常見於調用後端介面非同步獲取更新數據,而Action也是無法直接修改State的,還是需要通過Mutation來修改State的數據。最後,根據State的變化,渲染到視圖上。Vuex運行依賴Vue內部數據雙向綁定機制,需要new一個Vue對象來實現“響應式化”,所以Vuex是一個專門為Vue.js設計的狀態管理庫。

## 安裝

使用過Vuex的朋友一定知道,Vuex的安裝十分簡單,只需要提供一個store,然後執行下麵兩句代碼即完成的Vuex的引入。

```javascript
Vue.use(Vuex);

/*將store放入Vue創建時的option中*/
new Vue({
el: '#app',
store
});
```

那麼問題來了,Vuex是怎樣把store註入到Vue實例中去的呢?

Vue.js提供了[Vue.use](https://cn.vuejs.org/v2/api/#Vue-use)方法用來給Vue.js安裝插件,內部通過調用插件的install方法(當插件是一個對象的時候)來進行插件的安裝。

我們來看一下Vuex的install實現。

```javascript
/*暴露給外部的插件install方法,供Vue.use調用安裝插件*/
export function install (_Vue) {
if (Vue) {
/*避免重覆安裝(Vue.use內部也會檢測一次是否重覆安裝同一個插件)*/
if (process.env.NODE_ENV !== 'production') {
console.error(
'[vuex] already installed. Vue.use(Vuex) should be called only once.'
)
}
return
}
/*保存Vue,同時用於檢測是否重覆安裝*/
Vue = _Vue
/*將vuexInit混淆進Vue的beforeCreate(Vue2.0)或_init方法(Vue1.0)*/
applyMixin(Vue)
}
```

這段install代碼做了兩件事情,一件是防止Vuex被重覆安裝,另一件是執行applyMixin,目的是執行vuexInit方法初始化Vuex。Vuex針對Vue1.0與2.0分別進行了不同的處理,如果是Vue1.0,Vuex會將vuexInit方法放入Vue的_init方法中,而對於Vue2.0,則會將vuexinit混淆進Vue的beforeCreacte鉤子中。來看一下vuexInit的代碼。

```javascript
/*Vuex的init鉤子,會存入每一個Vue實例等鉤子列表*/
function vuexInit () {
const options = this.$options
// store injection
if (options.store) {
/*存在store其實代表的就是Root節點,直接執行store(function時)或者使用store(非function)*/
this.$store = typeof options.store === 'function'
? options.store()
: options.store
} else if (options.parent && options.parent.$store) {
/*子組件直接從父組件中獲取$store,這樣就保證了所有組件都公用了全局的同一份store*/
this.$store = options.parent.$store
}
}
```

vuexInit會嘗試從options中獲取store,如果當前組件是根組件(Root節點),則options中會存在store,直接獲取賦值給$store即可。如果當前組件非根組件,則通過options中的parent獲取父組件的$store引用。這樣一來,所有的組件都獲取到了同一份記憶體地址的Store實例,於是我們可以在每一個組件中通過this.$store愉快地訪問全局的Store實例了。

那麼,什麼是Store實例?

## Store

我們傳入到根組件到store,就是Store實例,用Vuex提供到Store方法構造。

```javascript
export default new Vuex.Store({
strict: true,
modules: {
moduleA,
moduleB
}
});
```

我們來看一下Store的實現。首先是構造函數。

```javascript
constructor (options = {}) {
// Auto install if it is not done yet and `window` has `Vue`.
// To allow users to avoid auto-installation in some cases,
// this code should be placed here. See #731
/*
在瀏覽器環境下,如果插件還未安裝(!Vue即判斷是否未安裝),則它會自動安裝。
它允許用戶在某些情況下避免自動安裝。
*/
if (!Vue && typeof window !== 'undefined' && window.Vue) {
install(window.Vue)
}

if (process.env.NODE_ENV !== 'production') {
assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`)
assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`)
assert(this instanceof Store, `Store must be called with the new operator.`)
}

const {
/*一個數組,包含應用在 store 上的插件方法。這些插件直接接收 store 作為唯一參數,可以監聽 mutation(用於外部地數據持久化、記錄或調試)或者提交 mutation (用於內部數據,例如 websocket 或 某些觀察者)*/
plugins = [],
/*使 Vuex store 進入嚴格模式,在嚴格模式下,任何 mutation 處理函數以外修改 Vuex state 都會拋出錯誤。*/
strict = false
} = options

/*從option中取出state,如果state是function則執行,最終得到一個對象*/
let {
state = {}
} = options
if (typeof state === 'function') {
state = state()
}

// store internal state
/* 用來判斷嚴格模式下是否是用mutation修改state的 */
this._committing = false
/* 存放action */
this._actions = Object.create(null)
/* 存放mutation */
this._mutations = Object.create(null)
/* 存放getter */
this._wrappedGetters = Object.create(null)
/* module收集器 */
this._modules = new ModuleCollection(options)
/* 根據namespace存放module */
this._modulesNamespaceMap = Object.create(null)
/* 存放訂閱者 */
this._subscribers = []
/* 用以實現Watch的Vue實例 */
this._watcherVM = new Vue()

// bind commit and dispatch to self
/*將dispatch與commit調用的this綁定為store對象本身,否則在組件內部this.dispatch時的this會指向組件的vm*/
const store = this
const { dispatch, commit } = this
/* 為dispatch與commit綁定this(Store實例本身) */
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
/*嚴格模式(使 Vuex store 進入嚴格模式,在嚴格模式下,任何 mutation 處理函數以外修改 Vuex state 都會拋出錯誤)*/
this.strict = strict

// init root module.
// this also recursively registers all sub-modules
// and collects all module getters inside this._wrappedGetters
/*初始化根module,這也同時遞歸註冊了所有子modle,收集所有module的getter到_wrappedGetters中去,this._modules.root代表根module才獨有保存的Module對象*/
installModule(this, state, [], this._modules.root)

// initialize the store vm, which is responsible for the reactivity
// (also registers _wrappedGetters as computed properties)
/* 通過vm重設store,新建Vue對象使用Vue內部的響應式實現註冊state以及computed */
resetStoreVM(this, state)

// apply plugins
/* 調用插件 */
plugins.forEach(plugin => plugin(this))

/* devtool插件 */
if (Vue.config.devtools) {
devtoolPlugin(this)
}
}
```

Store的構造類除了初始化一些內部變數以外,主要執行了installModule(初始化module)以及resetStoreVM(通過VM使store“響應式”)。

### installModule

installModule的作用主要是用為module加上namespace名字空間(如果有)後,註冊mutation、action以及getter,同時遞歸安裝所有子module。

```javascript
/*初始化module*/
function installModule (store, rootState, path, module, hot) {
/* 是否是根module */
const isRoot = !path.length
/* 獲取module的namespace */
const namespace = store._modules.getNamespace(path)

// register in namespace map
/* 如果有namespace則在_modulesNamespaceMap中註冊 */
if (module.namespaced) {
store._modulesNamespaceMap[namespace] = module
}

// set state
if (!isRoot && !hot) {
/* 獲取父級的state */
const parentState = getNestedState(rootState, path.slice(0, -1))
/* module的name */
const moduleName = path[path.length - 1]
store.`_withCommit`(() => {
/* 將子module設置稱響應式的 */
Vue.set(parentState, moduleName, module.state)
})
}

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

/* 遍歷註冊mutation */
module.forEachMutation((mutation, key) => {
const namespacedType = namespace + key
registerMutation(store, namespacedType, mutation, local)
})

/* 遍歷註冊action */
module.forEachAction((action, key) => {
const namespacedType = namespace + key
registerAction(store, namespacedType, action, local)
})

/* 遍歷註冊getter */
module.forEachGetter((getter, key) => {
const namespacedType = namespace + key
registerGetter(store, namespacedType, getter, local)
})

/* 遞歸安裝mudule */
module.forEachChild((child, key) => {
installModule(store, rootState, path.concat(key), child, hot)
})
}
```

### resetStoreVM

在說resetStoreVM之前,先來看一個小demo。

```javascript
let globalData = {
d: 'hello world'
};
new Vue({
data () {
return {
$$state: {
globalData
}
}
}
});

/* modify */
setTimeout(() => {
globalData.d = 'hi~';
}, 1000);

Vue.prototype.globalData = globalData;

/* 任意模板中 */
<div>{{globalData.d}}</div>
```

上述代碼在全局有一個globalData,它被傳入一個Vue對象的data中,之後在任意Vue模板中對該變數進行展示,因為此時globalData已經在Vue的prototype上了所以直接通過this.prototype訪問,也就是在模板中的{{prototype.d}}。此時,setTimeout在1s之後將globalData.d進行修改,我們發現模板中的globalData.d發生了變化。其實上述部分就是Vuex依賴Vue核心實現數據的“響應式化”。

不熟悉Vue.js響應式原理的同學可以通過筆者另一篇文章[響應式原理](https://github.com/answershuto/learnVue/blob/master/docs/%E5%93%8D%E5%BA%94%E5%BC%8F%E5%8E%9F%E7%90%86.MarkDown)瞭解Vue.js是如何進行數據雙向綁定的。

接著來看代碼。

```javascript
/* 通過vm重設store,新建Vue對象使用Vue內部的響應式實現註冊state以及computed */
function resetStoreVM (store, state, hot) {
/* 存放之前的vm對象 */
const oldVm = store._vm

// bind store public getters
store.getters = {}
const wrappedGetters = store._wrappedGetters
const computed = {}

/* 通過Object.defineProperty為每一個getter方法設置get方法,比如獲取this.$store.getters.test的時候獲取的是store._vm.test,也就是Vue對象的computed屬性 */
forEachValue(wrappedGetters, (fn, key) => {
// use computed to leverage its lazy-caching mechanism
computed[key] = () => 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的目的是在new一個Vue實例的過程中不會報出一切警告 */
Vue.config.silent = true
/* 這裡new了一個Vue對象,運用Vue內部的響應式實現註冊state以及computed*/
store._vm = new Vue({
data: {
$$state: state
},
computed
})
Vue.config.silent = silent

// enable strict mode for new vm
/* 使能嚴格模式,保證修改store只能通過mutation */
if (store.strict) {
enableStrictMode(store)
}

if (oldVm) {
/* 解除舊vm的state的引用,以及銷毀舊的Vue對象 */
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())
}
}
```

resetStoreVM首先會遍歷wrappedGetters,使用Object.defineProperty方法為每一個getter綁定上get方法,這樣我們就可以在組件里訪問this.$store.getter.test就等同於訪問store._vm.test。

```javascript
forEachValue(wrappedGetters, (fn, key) => {
// use computed to leverage its lazy-caching mechanism
computed[key] = () => fn(store)
Object.defineProperty(store.getters, key, {
get: () => store._vm[key],
enumerable: true // for local getters
})
})
```

之後Vuex採用了new一個Vue對象來實現數據的“響應式化”,運用Vue.js內部提供的數據雙向綁定功能來實現store的數據與視圖的同步更新。

```javascript
store._vm = new Vue({
data: {
$$state: state
},
computed
})
```

這時候我們訪問store._vm.test也就訪問了Vue實例中的屬性。

這兩步執行完以後,我們就可以通過this.$store.getter.test訪問vm中的test屬性了。

### 嚴格模式

Vuex的Store構造類的option有一個strict的參數,可以控制Vuex執行嚴格模式,嚴格模式下,所有修改state的操作必須通過mutation實現,否則會拋出錯誤。

```javascript
/* 使能嚴格模式 */
function enableStrictMode (store) {
store._vm.$watch(function () { return this._data.$$state }, () => {
if (process.env.NODE_ENV !== 'production') {
/* 檢測store中的_committing的值,如果是true代表不是通過mutation的方法修改的 */
assert(store._committing, `Do not mutate vuex store state outside mutation handlers.`)
}
}, { deep: true, sync: true })
}
```

首先,在嚴格模式下,Vuex會利用vm的$watch方法來觀察$$state,也就是Store的state,在它被修改的時候進入回調。我們發現,回調中只有一句話,用assert斷言來檢測store._committing,當store._committing為false的時候會觸發斷言,拋出異常。

我們發現,Store的commit方法中,執行mutation的語句是這樣的。

```javascript
this._withCommit(() => {
entry.forEach(function commitIterator (handler) {
handler(payload)
})
})
```

再來看看_withCommit的實現。

```javascript
_withCommit (fn) {
/* 調用withCommit修改state的值時會將store的committing值置為true,內部會有斷言檢查該值,在嚴格模式下只允許使用mutation來修改store中的值,而不允許直接修改store的數值 */
const committing = this._committing
this._committing = true
fn()
this._committing = committing
}
```

我們發現,通過commit(mutation)修改state數據的時候,會再調用mutation方法之前將committing置為true,接下來再通過mutation函數修改state中的數據,這時候觸發$watch中的回調斷言committing是不會拋出異常的(此時committing為true)。而當我們直接修改state的數據時,觸發$watch的回調執行斷言,這時committing為false,則會拋出異常。這就是Vuex的嚴格模式的實現。

接下來我們來看看Store提供的一些API。

### commit([mutation](https://vuex.vuejs.org/zh-cn/mutations.html))

```javascript
/* 調用mutation的commit方法 */
commit (_type, _payload, _options) {
// check object-style commit
/* 校驗參數 */
const {
type,
payload,
options
} = unifyObjectStyle(_type, _payload, _options)

const mutation = { type, payload }
/* 取出type對應的mutation的方法 */
const entry = this._mutations[type]
if (!entry) {
if (process.env.NODE_ENV !== 'production') {
console.error(`[vuex] unknown mutation type: ${type}`)
}
return
}
/* 執行mutation中的所有方法 */
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'
)
}
}
```

commit方法會根據type找到並調用_mutations中的所有type對應的mutation方法,所以當沒有namespace的時候,commit方法會觸發所有module中的mutation方法。再執行完所有的mutation之後會執行_subscribers中的所有訂閱者。我們來看一下_subscribers是什麼。

Store給外部提供了一個subscribe方法,用以註冊一個訂閱函數,會push到Store實例的_subscribers中,同時返回一個從_subscribers中註銷該訂閱者的方法。

```javascript
/* 註冊一個訂閱函數,返回取消訂閱的函數 */
subscribe (fn) {
const subs = this._subscribers
if (subs.indexOf(fn) < 0) {
subs.push(fn)
}
return () => {
const i = subs.indexOf(fn)
if (i > -1) {
subs.splice(i, 1)
}
}
}
```

在commit結束以後則會調用這些_subscribers中的訂閱者,這個訂閱者模式提供給外部一個監視state變化的可能。state通過mutation改變時,可以有效補獲這些變化。

### dispatch([action](https://vuex.vuejs.org/zh-cn/actions.html))

來看一下dispatch的實現。

```javascript
/* 調用action的dispatch方法 */
dispatch (_type, _payload) {
// check object-style dispatch
const {
type,
payload
} = unifyObjectStyle(_type, _payload)

/* actions中取出type對應的ation */
const entry = this._actions[type]
if (!entry) {
if (process.env.NODE_ENV !== 'production') {
console.error(`[vuex] unknown action type: ${type}`)
}
return
}

/* 是數組則包裝Promise形成一個新的Promise,只有一個則直接返回第0個 */
return entry.length > 1
? Promise.all(entry.map(handler => handler(payload)))
: entry[0](payload)
}
```

以及registerAction時候做的事情。

```javascript
/* 遍歷註冊action */
function registerAction (store, type, handler, local) {
/* 取出type對應的action */
const entry = store._actions[type] || (store._actions[type] = [])
entry.push(function wrappedActionHandler (payload, cb) {
let res = handler.call(store, {
dispatch: local.dispatch,
commit: local.commit,
getters: local.getters,
state: local.state,
rootGetters: store.getters,
rootState: store.state
}, payload, cb)
/* 判斷是否是Promise */
if (!isPromise(res)) {
/* 不是Promise對象的時候轉化稱Promise對象 */
res = Promise.resolve(res)
}
if (store._devtoolHook) {
/* 存在devtool插件的時候觸發vuex的error給devtool */
return res.catch(err => {
store._devtoolHook.emit('vuex:error', err)
throw err
})
} else {
return res
}
})
}
```

因為registerAction的時候將push進_actions的action進行了一層封裝(wrappedActionHandler),所以我們在進行dispatch的第一個參數中獲取state、commit等方法。之後,執行結果res會被進行判斷是否是Promise,不是則會進行一層封裝,將其轉化成Promise對象。dispatch時則從_actions中取出,只有一個的時候直接返回,否則用Promise.all處理再返回。

### watch

```javascript
/* 觀察一個getter方法 */
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)
}
```

熟悉Vue的朋友應該很熟悉watch這個方法。這裡採用了比較巧妙的設計,_watcherVM是一個Vue的實例,所以watch就可以直接採用了Vue內部的watch特性提供了一種觀察數據getter變動的方法。

### registerModule

```javascript
/* 註冊一個動態module,當業務進行非同步載入的時候,可以通過該介面進行註冊動態module */
registerModule (path, rawModule) {
/* 轉化稱Array */
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)
/*初始化module*/
installModule(this, this.state, path, this._modules.get(path))
// reset store to update getters...
/* 通過vm重設store,新建Vue對象使用Vue內部的響應式實現註冊state以及computed */
resetStoreVM(this, this.state)
}
```

registerModule用以註冊一個動態模塊,也就是在store創建以後再註冊模塊的時候用該介面。內部實現實際上也只有installModule與resetStoreVM兩個步驟,前面已經講過,這裡不再累述。

### unregisterModule

```javascript
/* 註銷一個動態module */
unregisterModule (path) {
/* 轉化稱Array */
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.`)
}

/*註銷*/
this._modules.unregister(path)
this._withCommit(() => {
/* 獲取父級的state */
const parentState = getNestedState(this.state, path.slice(0, -1))
/* 從父級中刪除 */
Vue.delete(parentState, path[path.length - 1])
})
/* 重製store */
resetStore(this)
}
```

同樣,與registerModule對應的方法unregisterModule,動態註銷模塊。實現方法是先從state中刪除模塊,然後用resetStore來重製store。

### resetStore

```javascript
/* 重製store */
function resetStore (store, hot) {
store._actions = Object.create(null)
store._mutations = Object.create(null)
store._wrappedGetters = Object.create(null)
store._modulesNamespaceMap = Object.create(null)
const state = store.state
// init all modules
installModule(store, state, [], store._modules.root, true)
// reset vm
resetStoreVM(store, state, hot)
}
```

這裡的resetStore其實也就是將store中的_actions等進行初始化以後,重新執行installModule與resetStoreVM來初始化module以及用Vue特性使其“響應式化”,這跟構造函數中的是一致的。

## 插件

Vue提供了一個非常好用的插件[Vue.js devtools](https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd)

```javascript
/* 從window對象的__VUE_DEVTOOLS_GLOBAL_HOOK__中獲取devtool插件 */
const devtoolHook =
typeof window !== 'undefined' &&
window.__VUE_DEVTOOLS_GLOBAL_HOOK__

export default function devtoolPlugin (store) {
if (!devtoolHook) return

/* devtoll插件實例存儲在store的_devtoolHook上 */
store._devtoolHook = devtoolHook

/* 出發vuex的初始化事件,並將store的引用地址傳給deltool插件,使插件獲取store的實例 */
devtoolHook.emit('vuex:init', store)

/* 監聽travel-to-state事件 */
devtoolHook.on('vuex:travel-to-state', targetState => {
/* 重製state */
store.replaceState(targetState)
})

/* 訂閱store的變化 */
store.subscribe((mutation, state) => {
devtoolHook.emit('vuex:mutation', mutation, state)
})
}
```

如果已經安裝了該插件,則會在windows對象上暴露一個__VUE_DEVTOOLS_GLOBAL_HOOK__。devtoolHook用在初始化的時候會觸發“vuex:init”事件通知插件,然後通過on方法監聽“vuex:travel-to-state”事件來重置state。最後通過Store的subscribe方法來添加一個訂閱者,在觸發commit方法修改mutation數據以後,該訂閱者會被通知,從而觸發“vuex:mutation”事件。

## 最後

Vuex是一個非常優秀的庫,代碼量不多且結構清晰,非常適合研究學習其內部實現。最近的一系列源碼閱讀也使我自己受益匪淺,寫這篇文章也希望可以幫助到更多想要學習探索Vuex內部實現原理的同學。

## 關於

作者:染陌

Email:[email protected] or [email protected]

Github: [https://github.com/answershuto](https://github.com/answershuto)

Blog:[http://answershuto.github.io/](http://answershuto.github.io/)

知乎主頁:[https://www.zhihu.com/people/cao-yang-49/activities](https://www.zhihu.com/people/cao-yang-49/activities)

知乎專欄:[https://zhuanlan.zhihu.com/ranmo](https://zhuanlan.zhihu.com/ranmo)

掘金: [https://juejin.im/user/58f87ae844d9040069ca7507](https://juejin.im/user/58f87ae844d9040069ca7507)

osChina:[https://my.oschina.net/u/3161824/blog](https://my.oschina.net/u/3161824/blog)

轉載請註明出處,謝謝。

歡迎關註我的公眾號

![](https://user-gold-cdn.xitu.io/2017/9/24/99de30e7ef948aabf132e91d543807a0)


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 定義類的方法,相信你會說,不就是在class語句下使用def () 就是定義類的方法了嘛,是的,這是定義的方法的一種,而且是最普通的方式 首先,我們已經知道有兩種方式: 1.普通方法: 1)與類無關的方法 2)與類相關的方法: 能夠被類直接調用的方法,而實例化因為沒有self綁定,會把自身作為參數傳 ...
  • 在js已有的基本類型(字元串,數值,布爾型,null和undefined)之外,es6引入了一種新的基本類型:==符號(Symbol)==。符號起初被設計用於創建對象私有成員。 符號沒有字面量形式,你可以使用全局Symbol函數來創建一個符號值。 Symbol還可以接受一個額外的參數用於描述符號值。 ...
  • js 數組的push方法,想必大家都知道是向數組末尾添加元素,但是有一個很關鍵的點需註意: 引自 MDN 返回值 當調用該方法時,新的 length 屬性值將被返回。 數組push之後返回的是length,而不是新的數組,如果不清楚這點,在使用過程中回遇到很大的坑。 順帶記一下其他幾個數組的方法返回 ...
  • var聲明與變數提升 使用var關鍵字聲明的變數,無論其實際聲明位置在何處,都會被視為聲明於所在函數的頂部(如果聲明不在任意函數內,則視為在全局作用域的頂部)。這就是所謂的變數提升。 塊級聲明 塊級聲明就是讓所聲明的變數在指定塊的作用域外無法被訪問。塊級作用域在如下情況被創建: 1. 一個函數內部 ...
  • 引言 JavaScript程式使用Unicode字元集編寫。Unicode是ASCII和Latin 1的超集,並支持地球上幾乎所有在使用的語言。ECMAScript3要求JavaScript的實現必須支持Unicode2.1及後續版本,ECMAScript5則要求支持Unicode3及後續版本。 區 ...
  • 1. 通過 2. 通過非同步請求 3. 通過jQuery ...
  • 備註:第一次翻譯技術文章,標題都糾結了好久不知道腫麽翻譯,如發現翻譯不當之處,可點擊github鏈接提交評論,thx~ 前幾天我為一個項目寫README文檔,我希望其他開發者能夠看到這個項目,並從中學到一些東西。突然我意識到,若放在幾年前,我寫作的過程中隨口提到的Node,npm,Homebrew, ...
  • 一.介紹 本篇是續上一篇的,引用類型的後篇,本篇主要是說基本包裝類型和個體內置對象。如果你能收穫一些知識,那我很高興,很滿足,哈哈哈,希望大家能愉快看完。如果你想學好一門技術,要不忘初心,方得始終。 二.基本包裝類型 先說明基本包裝類型也是屬於引用類型,是接著上一篇的,強調一下. 然後我們記住兩句話 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...