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 s t o r e , 这 里 的 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. s t o r e . s t a t e 就 是 用 s t o r e 中 的 v m . d a t a . state s t a t e , 而 这 个 $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中的方法,并进行调用。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值