從理解源碼開始學習Vuex(一)

Vuex

前言

9012年了,牛客的面經看的筆者由衷得以爲,這年頭,沒看過源碼,估計都不敢說本身是前端攻城獅了吧,再不折騰一下,估計連切圖都要輪不上了。雖說安心作個切圖仔仍是挺快樂的,不過說實在的,沒有夢想的切圖仔,和鹹魚有什麼分別。javascript

今天筆者要討論的就是如何實現一個縮小版的Vuex,本篇先你們熟悉一下他的內部結構大概是什麼樣的以及實現一個本身的stategettersmutationsactionsmodules部分筆者將在下一篇文章中進行介紹。前端

準備工做

用了這麼久Vuex了,咱們不妨大膽的想象幾個問題:vue

  • 咱們是怎麼在每一個Vue實例上都能拿到$store,它的實現原理是什麼。
  • Vuex 怎麼實現和Vue同樣,數據改變,界面自動更新。
  • gettersmutationsactions有什麼不一樣,怎麼實現。
  • mutationsactions有什麼不一樣,應用場景的區別,爲何。

接下來,咱們將從這幾個問題開始,揭開Vuex的神祕面紗java

Vuex解析

Vuex試探之旅,你值得擁(fang)有(qi)。git

友情提醒,下面的代碼不能直接用,只是筆者截取了片斷用於理解,文末會貼上完整代碼。es6

由於本篇不涉及modules,因此文中gettersmutationsactions實現將會和源碼有一些的出入,不過,原理是同樣的。github

如何給每一個實例注入$store

若是你們有本身註冊導入過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實現

其實說白了,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實現

其實從用法上看,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實現

其實吧,筆者以爲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方法的時候來指向對應的函數,並把statepayload傳入。

乍一看,彷佛和getters實現差不太多,只不過這裏沒用到屬性劫持了,而是給你包裝了一下調用方式。(反正筆者是這麼理解的)

actions實現

對於這個傢伙的用途,相信部分小夥伴也能輕鬆的說出,是的,官方推薦通常的應用場景就是處理一些異步事件,而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

相關文章
相關標籤/搜索