Vuex源碼解析

之前看了一下Vuex源碼,在這裏分享一下。
準備階段:
先去github上把源碼下載下來:

git clone https://github.com/vuejs/vuex.git
cd vuex
npm i

把依賴安裝一下,這樣就可以運行 npm run dev
之後在瀏覽器上打開http://localhost:8080/ ,就可以看到如下頁面:

隨便點擊進入一個項目,之後,在src目錄下做修改,比如打log,就會立刻在頁面上體現出來,這樣就比較方便我們瞭解各個變量具體是什麼。這也是我閱讀源碼的方法,不知道大家是如何閱讀源碼的呢。

正式開始
Vuex也是個vue插件,所以入口肯定是install,代碼很簡單:

export function install(_Vue) {
    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
    applyMixin(Vue)
}

這裏主要是做了一個限制,只能調用一次Vue.use(Vuex),接着就調用applyMixin函數,
這個函數也比較簡單:

export default function (Vue) {
    const version = Number(Vue.version.split('.')[0])

    if (version >= 2) {
        Vue.mixin({beforeCreate: vuexInit})
    } else {
        // override init and inject vuex init procedure
        // for 1.x backwards compatibility.
        const _init = Vue.prototype._init
        Vue.prototype._init = function (options = {}) {
            options.init = options.init
                ? [vuexInit].concat(options.init)
                : vuexInit
            _init.call(this, options)
        }
    }

    /**
     * Vuex init hook, injected into each instances init hooks list.
     */

    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
        }
    }
}

這裏做了一層兼容,兼容vue2.0之前的版本,主要是把vuexInit註冊到vue的生命週期中。vuexInit做的事情就是把store綁定到vue上,所以我們平常寫代碼可以使用this.store store就是我們實例化Vue的時候傳進去的store。
那這個store又是什麼呢?平常我們的代碼都是這樣寫的:

let store = new Vuex.Store({
    state,
    getters,
    actions,
    mutations,
    plugins: process.env.NODE_ENV !== 'production'
        ? [createLogger()]
        : []
})

new Vue({
    el: '#app',
    store,
    render: h => h(App)
})

可以看到,這個store就是Vuex.Store,所以,這個Vuex.Store就是Vuex的核心。接下來重點分析一下這個Store到底是怎麼運作的。
先看一下Store類的構造函數:(下面的代碼包含我的註釋,通過看這些註釋,可以更好的理解)

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
    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 {
        plugins = [],
        strict = false
    } = options

    // store internal state
    this._committing = false
    this._actions = Object.create(null)
    this._actionSubscribers = []
    this._mutations = Object.create(null)
    this._wrappedGetters = Object.create(null)
    // 這裏需要關注一下
    this._modules = new ModuleCollection(options)
    this._modulesNamespaceMap = Object.create(null)
    this._subscribers = []
    this._watcherVM = new Vue()

    // bind commit and dispatch to self
    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)
    }

    // strict mode
    this.strict = strict

    const state = this._modules.root.state

    // init root module.
    // this also recursively registers all sub-modules
    // and collects all module getters inside this._wrappedGetters
    installModule(this, state, [], this._modules.root)

    // initialize the store vm, which is responsible for the reactivity
    // (also registers _wrappedGetters as computed properties)
    resetStoreVM(this, state)

    // apply plugins
    plugins.forEach(plugin => plugin(this))

    if (Vue.config.devtools) {
        devtoolPlugin(this)
    }
}

這裏的代碼邏輯比較清晰,一開始是對一些成員變量做初始化,這裏比較需要關注的是:
this._modules = new ModuleCollection(options)
ModuleCollection的代碼在module-collection.js中,ModuleCollection類主要是把配置信息註冊到他的成員變量root中,root是Module類的實例,用於配置信息。
最重要的就是這兩行代碼:

// init root module.
// this also recursively registers all sub-modules
// and collects all module getters inside this._wrappedGetters
installModule(this, state, [], this._modules.root)

// initialize the store vm, which is responsible for the reactivity
// (also registers _wrappedGetters as computed properties)
resetStoreVM(this, state)

installModule的邏輯就是把配置中的getters、actions、mutations註冊到Store中的_mutations、_actions、_wrappedGetters中。代碼如下:

function installModule(store, rootState, path, module, hot) {
    const isRoot = !path.length
    const namespace = store._modules.getNamespace(path)

    // register in namespace map
    if (module.namespaced) {
        store._modulesNamespaceMap[namespace] = module
    }

    // set state
    if (!isRoot && !hot) {
        const parentState = getNestedState(rootState, path.slice(0, -1))
        const moduleName = path[path.length - 1]
        store._withCommit(() => {
            Vue.set(parentState, moduleName, module.state)
        })
    }

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

    module.forEachMutation((mutation, key) => {
        const namespacedType = namespace + key
        registerMutation(store, namespacedType, mutation, local)
    })

    module.forEachAction((action, key) => {
        const type = action.root ? key : namespace + key
        const handler = action.handler || action
        registerAction(store, type, handler, local)
    })

    module.forEachGetter((getter, key) => {
        const namespacedType = namespace + key
        registerGetter(store, namespacedType, getter, local)
    })

    module.forEachChild((child, key) => {
        installModule(store, rootState, path.concat(key), child, hot)
    })
}

這裏的module就是Store._modules.root,是Module類的實例,所以,module.forEachMutation就是遍歷new Vuex.Store時傳進去的mutations對象,然後把處理函數放到_mutations中,代碼如下:

module.forEachMutation((mutation, key) => {
// 這裏namespace先不去管它
    const namespacedType = namespace + key
    registerMutation(store, namespacedType, mutation, local)
})


function registerMutation(store, type, handler, local) {
    const entry = store._mutations[type] || (store._mutations[type] = [])
    entry.push(function wrappedMutationHandler(payload) {
        handler.call(store, local.state, payload)
    })
}

看到這行代碼:
handler.call(store, local.state, payload)
再結合我們平常定義mutations:

  mutations: {
    increment (state) {
      // 變更狀態
      state.count++
    }
  }

可以看到,我們mutations中的state就是這個local.state,

之後是action的註冊,註冊action的代碼如下:

function registerAction(store, type, handler, local) {
    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)
        if (!isPromise(res)) {
            res = Promise.resolve(res)
        }
        if (store._devtoolHook) {
            return res.catch(err => {
                store._devtoolHook.emit('vuex:error', err)
                throw err
            })
        } else {
            return res
        }
    })
}

從上面我們可以看到,我們平常定義action的時候,第一個參數對象可以使用dispatch、commit等。這裏需要注意的是:

        if (!isPromise(res)) {
            res = Promise.resolve(res)
        }

這裏會把handle調用的結果改成Promise,所以我們可以使用:

store.dispatch('actionA').then(() => {
  // ...
})

最後是getter的註冊,跟上面action差不多,這裏就不再敖述。

接下來我們再看一下resetStoreVM函數。再看resetStoreVM之前,我們先看構造函數下方的這段代碼:

get state() {
    return this._vm._data.$$state
}

set state(v) {
    if (process.env.NODE_ENV !== 'production') {
        assert(false, `use store.replaceState() to explicit replace store state.`)
    }
}

看到這裏我們知道,我們平常使用
return this.$store.state.xxx
原來使用的Store類中的_vm._data.$$state,那這個變量又是什麼呢?看看resetStoreVM我們就知道了。

// 這裏的state是_modules.root.state,這個也就是配置時的state
function resetStoreVM(store, state, hot) {
    const oldVm = store._vm

    // bind store public getters
    store.getters = {}
    const wrappedGetters = store._wrappedGetters
    const 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
    store._vm = new Vue({
        data: {
            $$state: state
        },
        computed
    })
    Vue.config.silent = silent

    // enable strict mode for new vm
    if (store.strict) {
        enableStrictMode(store)
    }

    if (oldVm) {
        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())
    }
}

重要是這段代碼:

    store._vm = new Vue({
        data: {
            $$state: state
		},
		computed
	})
```
看到這裏,我們知道,原來_vm就是一個Vue實例,data中的$$state就是我們配置時的state。這裏有一點需要注意,vue實例會把構造函數參數中的data放到_data裏面,所以使用_vm._data.$$state可以訪問。在這裏,還有一段有意思的代碼:




<div class="se-preview-section-delimiter"></div>

```javascript
    const wrappedGetters = store._wrappedGetters
    const 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
        })
    })

在之前我們已經知道,_wrappedGetters裏面存放的就是getter中的配置,所以,這裏就是把_wrappedGetters中的屬性映射到store.getter上,但是它返回的是store._vm中的屬性,store._vm中的屬性就是把computed當做計算屬性。

到這裏,我們知道,Store類的構造函數主要就是處理配置信息,其中state放到_modules.root.state中,mutations放在_mutations中,actions放到_actions中,getters放到_wrappedGetters。我們平常用的this.store.statestorevm.data.state $state就是配置時的state,還有我們用的getter,就是_vm的計算屬性。

接下來我們看下commit和dispatch是怎麼運作的,整個vuex也就差不多瞭解了。

commit的代碼如下:

commit(_type, _payload, _options) {
    // check object-style commit
    const {
        type,
        payload,
        options
    } = unifyObjectStyle(_type, _payload, _options)

    const mutation = {type, payload}
    const entry = this._mutations[type]
    if (!entry) {
        if (process.env.NODE_ENV !== 'production') {
            console.error(`[vuex] unknown mutation type: ${type}`)
        }
        return
    }
    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邏輯其實就是遍歷_mutations中對應的handler,並調用。

再看下dispatch代碼:

dispatch(_type, _payload) {
    // check object-style dispatch
    const {
        type,
        payload
    } = unifyObjectStyle(_type, _payload)

    const action = {type, payload}
    const entry = this._actions[type]
    if (!entry) {
        if (process.env.NODE_ENV !== 'production') {
            console.error(`[vuex] unknown action type: ${type}`)
        }
        return
    }

    this._actionSubscribers.forEach(sub => sub(action, this.state))

    return entry.length > 1
        ? Promise.all(entry.map(handler => handler(payload)))
    : entry[0](payload)
}

dispatch的邏輯也很清晰,就是把_actions中的處理函數執行一下,並返回。

到這裏,Vuex的整個流程就已經介紹完畢了。

總結
Vuex的整體流程還算比較清晰:
1. 對配置信息進行處理,state信息存放在_vm._data.$$state中,getters信息存放在_wrappedGetters中,並映射到Store的getters對象,getters對象的屬性再映射到_vm屬性中,mutations信息存放在_mutations中、actions的信息存放到_actions。
2. 當調用commit時,就是遍歷_mutations中的方法,並進行調用。
3. 當調用dispatch時,就是遍歷_actions中的方法,並進行調用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章