9012年了,牛客的面經看的筆者由衷得以爲,這年頭,沒看過源碼,估計都不敢說本身是前端攻城獅了吧,再不折騰一下,估計連切圖都要輪不上了。雖說安心作個切圖仔仍是挺快樂的,不過說實在的,沒有夢想的切圖仔,和鹹魚有什麼分別。javascript
今天筆者要討論的就是如何實現一個縮小版的Vuex
,本篇先你們熟悉一下他的內部結構大概是什麼樣的以及實現一個本身的state
、getters
、mutations
、actions
,modules
部分筆者將在下一篇文章中進行介紹。前端
用了這麼久Vuex
了,咱們不妨大膽的想象幾個問題:vue
Vue
實例上都能拿到$store
,它的實現原理是什麼。Vuex
怎麼實現和Vue
同樣,數據改變,界面自動更新。getters
與mutations
、actions
有什麼不一樣,怎麼實現。mutations
與actions
有什麼不一樣,應用場景的區別,爲何。接下來,咱們將從這幾個問題開始,揭開Vuex
的神祕面紗java
Vuex試探之旅,你值得擁(fang)有(qi)。git
友情提醒,下面的代碼不能直接用,只是筆者截取了片斷用於理解,文末會貼上完整代碼。es6
由於本篇不涉及modules
,因此文中getters
、mutations
、actions
實現將會和源碼有一些的出入,不過,原理是同樣的。github
若是你們有本身註冊導入過Vuex
,那麼相信你們對這樣的過程天然是瞭然於心:vuex
import Vuex from 'vue'
Vue.use(Vuex)
const store=new Vuex.Store({...})
new Vue
的地方放上store
咱們從第二個步驟開始研究。異步
至於那些問
import
怎麼用的大哥我只能默默地說一句,9012年了,沒接觸過es6
你是怎麼學完Vue.js
的。函數
顧名思義,use
就是用的意思,這其實就涉及到Vue
的這一個插件安裝機制了,它會默認去找須要安裝的模塊的install
方法,而後把Vue
以及其餘參數傳入你的install
方法,而後呢,咱們就能夠在這一步上動點手腳了。
話很少說,看(代)碼:
let _Vue;
const install = (vm, options) => {
_Vue = vm;
_Vue.mixin({
beforeCreate() {
if (this.$parent) {
this.$store = this.$parent.$store
} else {
this.$store = this.$options && this.$options.store
}
}
})
}
export default {install}
複製代碼
首先咱們會先把Vue
保存一下,留待他用(管他待會用不用,先留着)。而後接下來就是咱們上面提出的第一個問題怎麼實現的答案了,使用Vue
內部的mixin
方法,給每一個實例混入一個鉤子函數。
什麼是混入,這裏僞裝你們都知不道,稍微介紹一下。以上面代碼爲例,差很少就是給每一個實例都添加一個
beforeCreate
方法,它不會覆蓋原有的鉤子函數,而是會一塊兒執行。
而後咱們能夠先判斷是不是子組件,若是是就拿到它父組件的$store
賦值給當前子組件的store
上,若是不是子組件,就能夠從$options
上拿到$store
,這樣一來,全部Vue
實例就都有了$store
屬性了。
其實說白了,
state
不就是一個儲存一些屬性的對象而已,換了張臉我仍是認識你王麻子。
上(代)碼:
class Store {
constructor(options) {
/**保存一份到自己實例 */
this._options = options;
}
get state() {
return this._options.state
}
}
複製代碼
劫持一下獲取方式,實際上就是在訪問你傳進來的對象。固然,若是隻是這麼寫仍是有點問題的,它無法根據數據改變來自動讓界面更新。
其實這個問題真挺好解決的,筆者在問題中都已經提醒你了,不信你回去再看看
咱們借用Vue
實例具備數據界面綁定的特性,因而咱們給state
稍微包裝一下就能夠實現咱們要的效果了。
/**借用Vue的雙向綁定機制讓Vuex中data變化實時更新界面 */
this.vm = new _Vue({
data: {
state: options.state
}
})
複製代碼
而後再修改一下對應的get
方法
get state() {
return this.vm.state
}
複製代碼
其實從用法上看,getters
用法和咱們的computed
真挺像的。從表現形式上看,它其實根本上就是個函數,只不過人家內部代替你執行了。
/**簡化代碼,封裝遍歷方法 */
const forEach = (obj, callback) => {
Object.keys(obj).forEach((key) => {
callback(key, obj[key])
})
}
/**保存getters */
this.getters = {};
let getters = this._options.getters || {}
/**遍歷保存傳入的getters,監聽狀態改變從新執行該函數 */
forEach(getters, (getterName, fn) => {
Object.defineProperty(this.getters, getterName, {
get: () => {
return fn(this.state)
}
})
})
複製代碼
這裏咱們遍歷了用戶傳入的getters
對象,而後把拿到的屬性名寫入到實例的getters
對象上,並綁定了對應的get
方法。這個地方咱們用上了咱們熟悉的屬性劫持來控制怎麼給用戶返回咱們想給他的值。
同時,由於getter
中的第一個參數是state
對象,因此咱們在執行get
方法的時候會把state
做爲參數傳入執行,並返回函數執行結果給用戶。
彷佛實現起來並不怎麼難看懂,估計部分小夥伴會想,這貨該不會在訛我吧,怎麼可能這麼簡單。
其實吧,筆者以爲mutations
這個東東有點像咱們用過的methods
對象,一樣是一個對象上綁定了不少函數,只不過用法上面看上去彷佛不太同樣,咱們通常都是使用commit
方法來調用一個mutation
函數,而後傳入兩個參數state、payload
。
看到這個payload
,就會有小夥伴問了,啥是payload
啊?官方文檔稱之爲載荷,名字挺高大上的哈,其實就是接收用戶傳入的參數。
來看看筆者的實現吧。
/**保存mutations */\
constructor(options) {
this.mutations = {};
let mutations = this._options.mutations || {};
forEach(mutations, (mutationName, fn) => {
this.mutations[mutationName] = (payload) => {
return fn(this.state, payload)
}
})
}
...
commit = (type, payload) => {
this.mutations[type](payload);
}
複製代碼
先把用戶傳入的mutations
對象的屬性和方法保存到Vuex
實例上,而後讓用戶調用commit
方法的時候來指向對應的函數,並把state
和payload
傳入。
乍一看,彷佛和getters
實現差不太多,只不過這裏沒用到屬性劫持了,而是給你包裝了一下調用方式。(反正筆者是這麼理解的)
對於這個傢伙的用途,相信部分小夥伴也能輕鬆的說出,是的,官方推薦通常的應用場景就是處理一些異步事件,而mutations
通常用於處理同步事件。下面貼上官方的一張概念圖你就能理解了。
這時候又有小夥伴要說了,我用
mutations
同樣的能夠實現啊。是的,用mutations
固然也行,不過只是不符合Vuex
的設計理念而已。
若是對於一些異步事件使用mutations
會出現devtools
沒法捕獲到這個事件的記錄,因此咱們最好仍是走走尋常路,跟着官方走吧。
/**保存actions */
constructor(options) {
this.actions = {};
let actions = this._options.actions || {};
forEach(actions, (actionName, fn) => {
this.actions[actionName] = (payload) => {
return fn(this, payload)
}
})
}
...
dispatch = (type, payload) => {
this.actions[type](payload)
}
複製代碼
它實現起來仍是挺像mutations
的(畢竟兩個好基友嘛),他們從代碼層次上看差不太多,只是在傳參方面和調用方法方面有點差別,一個用commit
,參數是state,payload
;一個是dispatch
,傳參是一個Vuex
實例(實際上並非的,由於涉及到modules
,下文將會講到)。
具體代碼實現和mutations
差很少,筆者這就很少囉嗦了,不過在這裏筆者仍是要提醒一下,由於通常來講咱們在使用actions
的時候都是用的解構,獲取commit
,以及一些其餘參數,因此咱們在這裏須要注意下this
指向的問題,筆者這裏用的是箭頭函數來解決了這個問題。
let _Vue;
/**簡化代碼,封裝遍歷方法 */
const forEach = (obj, callback) => {
Object.keys(obj).forEach((key) => {
callback(key, obj[key])
})
}
class Store {
constructor(options) {
/**借用Vue的雙向綁定機制讓Vuex中data變化實時更新界面 */
this.vm = new _Vue({
data: {
state: options.state
}
})
/**保存一份到自己實例 */
this._options = options;
/**保存getters */
this.getters = {};
let getters = this._options.getters || {}
/**遍歷保存傳入的getters,監聽狀態改變從新執行該函數 */
forEach(getters, (getterName, fn) => {
Object.defineProperty(this.getters, getterName, {
get: () => {
return fn(this.state)
}
})
})
/**保存mutations */
this.mutations = {};
let mutations = this._options.mutations || {};
forEach(mutations, (mutationName, fn) => {
this.mutations[mutationName] = (payload) => {
return fn(this.state, payload)
}
})
/**保存actions */
this.actions = {};
let actions = this._options.actions || {};
forEach(actions, (actionName, fn) => {
this.actions[actionName] = (payload) => {
return fn(this, payload)
}
})
}
get state() {
return this.vm.state
}
commit = (type, payload) => {
this.mutations[type](payload);
}
dispatch = (type, payload) => {
this.actions[type](payload)
}
}
const install = (vm, options) => {
_Vue = vm;
_Vue.mixin({
beforeCreate() {
if (this.$parent) {
this.$store = this.$parent.$store
} else {
this.$store = this.$options && this.$options.store
}
}
})
}
export default {
install,
Store
};
複製代碼
修行不易,前端的世界老是突飛猛進,要跟上它的步伐仍是要多折騰折騰。
今天筆者就暫時說到這了,小夥伴以爲有幫助的話就給筆者點個讚唄。
貼上筆者我的網站地址: 煙雨的我的博客
源碼github地址:my_vuex