Vuex源碼閱讀筆記
俗話說得好,沒有無緣無故的愛,也沒有無緣無故的恨,更不會無緣無故的去閱讀別人的源代碼。之所以會去閱讀Vuex的源代碼,是因為在剛開始接觸Vuex時,就在官方文檔的Actions部分,看到這么一句:
// the simplest action
function increment (store) {
store.dispatch('INCREMENT')
}
// a action with additional arguments
// with ES2015 argument destructuring
function incrementBy ({ dispatch }, amount) {
dispatch('INCREMENT', amount)
}
上面的Action還好說,能看懂,但是下面使用ES6寫法的Action是什么鬼呀喂(摔!)
雖然知道有解構賦值,但是那個 { dispatch } 又是從哪兒冒出來的呀喂!明明我在調用時,沒有傳這個參數呀!
之前因為趕項目進度,所以抱著能用就行的態度,也就沒管那么多。如今有了空閑時間,必須好好鉆研一下呀。
而鉆研最好的方式,就是閱讀Vuex的源代碼。這樣就能弄清楚,那個 { dispatch } 到底從哪兒冒出來的。
Vuex源代碼簡介
Vuex的源代碼量挺少的,加起來也才600行不到,但是其中大量使用了ES6的語法,且部分功能(如Vuex初始化)使用到了Vue。所以讀起來還是有些費勁的。
整個Vuex的源代碼,核心內容包括兩部分。一部分是Store的構造函數,另一部分則是Vuex的初始化函數。
而剛才問題的答案,就在第二部分。
問題場景還原
首先要介紹的,就是Vuex在Vue項目中的初始化。這兒貼一段代碼:
首先是Vuex中,我寫的Actions源代碼:
// global/Vuex/action.js
export const getMe = ({ dispatch }) => {
/**
* 異步操作,獲取用戶信息,并存入Vuex的state中
*/
res.user.get_me()
.then(data => {
dispatch('GET_ME', data)
})
.catch(err => {
console.log(err)
})
}
這個則是頂層組件,調用store的地方。由于Vuex的特點,store只需要在最頂層的組件聲明一次。
<template>
<div id="wrapper">
<router-view></router-view>
</div>
</template>
<script type="text/javascript">
import store from './Vuex/store.js'
export default {
store
}
</script>
接下來則是組件中,則是實際調用Vuex的代碼。
// index.vue
import { getMe } from './../global/Vuex/action'
export default {
vuex: {
actions: {
getMe
},
getters: {
// 從state中獲取信息
user: state => state.user
}
},
ready() {
// 開始獲取用戶信息
this.getMe()
}
}
在這兒,可以很明顯的看出,我在使用 this.getMe() 時,是沒有任何參數的。但是在 getMe 函數的定義中,是需要解構賦值出 {dispatch} 的。
就好比說這個:
function getX({ x }) {
console.log(x)
}
getX({ x: 3, y: 5 })
// 3
你得傳入相應的參數,才能進行解構賦值。
同時,我注意到在Vuex的Actions調用,需要在Vue的options的Vuex.actions中先聲明,之后才能使用。
那么,一定是Vuex對這個Action動了手腳。(逃)
而動手腳的代碼,就存在于Vuex源代碼的 override.js 中。這個文件,是用于初始化Vuex的。
Vuex的初始化
在 override.js 中,有個 vuexInit 的函數。看名字就知道,這是拿來初始化Vuex的。
在代碼開頭,有這么一句:
const options = this.$options
const { store, vuex } = options
// 感覺解構賦值真的很棒,這樣寫能省很多時間。
// 下面的是老寫法
// const store = options.store
// const vuex = options.vuex
在這兒,用于是在Vue中調用,所以this指向Vue,而this.$options則是Vue的配置項。
也就是寫Vue組件時的:
export default {……一些配置}
這里,就把Vue配置項的store和vuex抽離出來了。
搜尋store
接下來,則看到了Vuex源代碼的精妙之處:
// store injection
if (store) {
this.$store = store
} else if (options.parent && options.parent.$store) {
this.$store = options.parent.$store
}
解構賦值并不是一定成功的,如果store在options中不存在,那么store就會是undefined。但是我們需要找store。
于是Vuex提供了向父級(Vue中的功能)尋找store的功能。不難看出,這兒父級的$store如果不存在,那么其實他也會到自己的父級去尋找。直到找到為止。
就想一條鎖鏈一樣,一層一層的連到最頂部store。所以在沒有找到時,Vuex會給你報個錯誤。
// 聲明了Vuex但沒有找到store時的狀況
if (vuex) {
if (!this.$store) {
console.warn(
'[vuex] store not injected. make sure to ' +
'provide the store option in your root component.'
)
}
對Vuex聲明的內容,進行改造
接下來,則是對Vuex聲明的內容,進行改造。
首先的是獲取Vuex對象的內容:
let { state, getters, actions } = vuex
同時,在這兒還看到了對過時API的處理。感覺算是意料之外的驚喜。
// handle deprecated state option
// 如果使用state而不是getters來獲取Store的數據,則會提示你state已經過時的,你需要使用新的api。
// 但是,這兒也做了兼容,確保升級時服務不會掛掉。
if (state && !getters) {
console.warn(
'[vuex] vuex.state option will been deprecated in 1.0. ' +
'Use vuex.getters instead.'
)
getters = state
}
接下來,則是對getters和actions的處理:
// getters
if (getters) {
options.computed = options.computed || {}
for (let key in getters) {
defineVuexGetter(this, key, getters[key])
}
}
// actions
if (actions) {
options.methods = options.methods || {}
for (let key in actions) {
options.methods[key] = makeBoundAction(this.$store, actions[key], key)
}
}
可以看出,在這兒對getters和actions都進行了額外處理。在這兒,我們講述actions的額外處理,至于getters,涉及了過多的Vue,而我不是很熟悉。等我多鉆研后,再寫吧。
Actions的改造
對整個Actions的改造,首先是Vuex的檢測:
// actions
if (actions) {
// options.methods是Vue的methods選項
options.methods = options.methods || {}
for (let key in actions) {
options.methods[key] = makeBoundAction(this.$store, actions[key], key)
}
}
在這兒,我們一點一點的剖析。可以看出,所有的actions,都會被 makeBoundAction 函數處理,并加入Vue的methods選項中。
那么看來, makeBoundAction 函數就是我要找的答案了。
接下來貼出 makeBoundAction 函數的源代碼:
/**
* Make a bound-to-store version of a raw action function.
*
* @param {Store} store
* @param {Function} action
* @param {String} key
*/
function makeBoundAction(store, action, key) {
if (typeof action !== 'function') {
console.warn(`[vuex] Action bound to key 'vuex.actions.${key}' is not a function.`)
}
return function vuexBoundAction(...args) {
return action.call(this, store, ...args)
}
}
事情到這兒,其實已經豁然明朗了。
我在Vuex中傳入的actions,實際會被處理為 vuexBoundAction ,并加入options.methods中。
在調用這個函數時,實際上的action會使用call,來改變this指向并傳入store作為第一個參數。而store是有dispatch這個函數的。
那么,在我傳入 {dispatch} 時,自然而然就會解構賦值。
這樣的話,也形成了閉包,確保action能訪問到store。
結語
今天應該算是解決了心中的一個大疑惑,還是那句話:
沒有無緣無故的愛,也沒有無緣無故的恨,更沒有無緣無故冒出來的代碼。
整個源代碼讀下來一遍,雖然有些部分不太理解,但是對ES6和一些代碼的使用的理解又加深了一步。比如這回就鞏固了我關于ES6解構賦值的知識。而且還收獲了很多別的東西。總而言之,收獲頗豐~最后的,依然是那句話:前端路漫漫,且行且歌。