这几天忙啊,有绝地求生要上分,英雄联盟新赛季须要上分,就懒着什么也没写,很惭愧。这个vuex,vue-router,vue的源码我半个月前就看的差很少了,可是懒,哈哈。
下面是vuex的源码分析
在分析源码的时候咱们能够写几个例子来进行了解,必定不要闭门造车,多写几个例子,也就明白了
在vuex源码中选择了example/counter这个文件做为例子来进行理解
counter/store.js是vuex的核心文件,这个例子比较简单,若是比较复杂咱们能够采起分模块来让代码结构更加清楚,如何分模块请在vuex的官网中看如何使用。
咱们来看看store.js的代码:vue
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) const state = { count: 0 } const mutations = { increment (state) { state.count++ }, decrement (state) { state.count-- } } const actions = { increment: ({ commit }) => commit('increment'), decrement: ({ commit }) => commit('decrement'), incrementIfOdd ({ commit, state }) { if ((state.count + 1) % 2 === 0) { commit('increment') } }, incrementAsync ({ commit }) { return new Promise((resolve, reject) => { setTimeout(() => { commit('increment') resolve() }, 1000) }) } } const getters = { evenOrOdd: state => state.count % 2 === 0 ? 'even' : 'odd' } actions, export default new Vuex.Store({ state, getters, actions, mutations })
在代码中咱们实例化了Vuex.Store,每个 Vuex 应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的状态 。在源码中咱们来看看Store是怎样的一个构造函数(由于代码太多,我把一些判断进行省略,让你们看的更清楚)vue-router
src/store.jsvuex
export class Store { constructor (options = {}) { if (!Vue && typeof window !== 'undefined' && window.Vue) { install(window.Vue) } 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 installModule(this, state, [], this._modules.root) resetStoreVM(this, state) plugins.forEach(plugin => plugin(this)) if (Vue.config.devtools) { devtoolPlugin(this) } } 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.`) } } 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' ) } } dispatch (_type, _payload) { 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) } subscribe (fn) { return genericSubscribe(fn, this._subscribers) } subscribeAction (fn) { return genericSubscribe(fn, this._actionSubscribers) } watch (getter, cb, options) { if (process.env.NODE_ENV !== 'production') { assert(typeof getter === 'function', `store.watch only accepts a function.`) } return this._watcherVM.$watch(() => getter(this.state, this.getters), cb, options) } replaceState (state) { this._withCommit(() => { this._vm._data.$$state = state }) } registerModule (path, rawModule, options = {}) { if (typeof path === 'string') path = [path] if (process.env.NODE_ENV !== 'production') { assert(Array.isArray(path), `module path must be a string or an Array.`) assert(path.length > 0, 'cannot register the root module by using registerModule.') } this._modules.register(path, rawModule) installModule(this, this.state, path, this._modules.get(path), options.preserveState) resetStoreVM(this, this.state) } hotUpdate (newOptions) { this._modules.update(newOptions) resetStore(this, true) } _withCommit (fn) { const committing = this._committing this._committing = true fn() this._committing = committing } }
首先判断window.Vue是否存在,若是不存在就安装Vue, 而后初始化定义,plugins表示应用的插件,strict表示是否为严格模式。而后对store
的一系列属性进行初始化,来看看这些属性分别表明的是什么数组
后面获取dispatch和commit而后将this.dipatch和commit进行绑定,咱们来看看app
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)) }
commit有三个参数,_type表示mutation的类型,_playload表示额外的参数,options表示一些配置。unifyObjectStyle()这个函数就不列出来了,它的做用就是判断传入的_type是否是对象,若是是对象进行响应简单的调整。而后就是根据type来获取相应的mutation,若是不存在就报错,若是有就调用this._witchCommit。下面是_withCommit的具体实现函数
_withCommit (fn) { const committing = this._committing this._committing = true fn() this._committing = committing }
函数中屡次提到了_committing,这个的做用咱们在初始化Store的时候已经提到了更改状态只能经过mutatiton进行更改,其余方式都不行,经过检测committing的值咱们就能够查看在更改状态的时候是否发生错误
继续来看commit函数,this._withCommitting对获取到的mutation进行提交,而后遍历this._subscribers,调用回调函数,而且将state传入。整体来讲commit函数的做用就是提交Mutation,遍历_subscribers调用回调函数
下面是dispatch的代码源码分析
dispatch (_type, _payload) { const action = { type, payload } const entry = this._actions[type] this._actionSubscribers.forEach(sub => sub(action, this.state)) return entry.length > 1 ? Promise.all(entry.map(handler => handler(payload))) : entry[0](payload) } }
和commit函数相似,首先获取type对象的actions,而后遍历action订阅者进行回调,对actions的长度进行判断,当actions只有一个的时候进行将payload传入,不然遍历传入参数,返回一个Promise.
commit和dispatch函数谈完,咱们回到store.js中的Store构造函数
this.strict表示是否开启严格模式,严格模式下咱们可以观测到state的变化状况,线上环境记得关闭严格模式。
this._modules.root.state就是获取根模块的状态,而后调用installModule(),下面是this
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.forEachChild((child, key) => { installModule(store, rootState, path.concat(key), child, hot) }) }
installModule()包括5个参数store表示当前store实例,rootState表示根组件状态,path表示组件路径数组,module表示当前安装的模块,hot 当动态改变 modules 或者热更新的时候为 true
首先进经过计算组件路径长度判断是否是根组件,而后获取对应的命名空间,若是当前模块存在namepaced那么就在store上添加模块
若是不是根组件和hot为false的状况,那么先经过getNestedState找到rootState的父组件的state,由于path表示组件路径数组,那么最后一个元素就是该组件的路径,最后经过_withComment()函数提交Mutation,spa
Vue.set(parentState, moduleName, module.state)
这是Vue的部分,就是在parentState添加属性moduleName,而且值为module.state插件
const local = module.context = makeLocalContext(store, namespace, path)
这段代码里边有一个函数makeLocalContext
function makeLocalContext (store, namespace, path) { const noNamespace = namespace === '' const local = { dispatch: noNamespace ? store.dispatch : ... commit: noNamespace ? store.commit : ... } Object.defineProperties(local, { getters: { get: noNamespace ? () => store.getters : () => makeLocalGetters(store, namespace) }, state: { get: () => getNestedState(store.state, path) } }) return local }
传入的三个参数store, namspace, path,store表示Vuex.Store的实例,namspace表示命名空间,path组件路径。总体的意思就是本地化dispatch,commit,getter和state,意思就是新建一个对象上面有dispatch,commit,getter 和 state方法
那么installModule的那段代码就是绑定方法在module.context和local上。继续看后面的首先遍历module的mutation,经过mutation 和 key 首先获取namespacedType,而后调用registerMutation()方法,咱们来看看
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) }) }
是否是感受这个函数有些熟悉,store表示当前Store实例,type表示类型,handler表示mutation执行的回调函数,local表示本地化后的一个变量。在最开始的时候咱们就用到了这些东西,例如
const mutations = { increment (state) { state.count++ } ...
首先获取type对应的mutation,而后向mutations数组push进去包装后的hander函数。
module.forEachChild((child, key) => { installModule(store, rootState, path.concat(key), child, hot) })
遍历module,对module的每一个子模块都调用installModule()方法
讲完installModule()方法,咱们继续往下看resetStoreVM()函数
function resetStoreVM (store, state, hot) { const oldVm = store._vm store.getters = {} const wrappedGetters = store._wrappedGetters const computed = {} forEachValue(wrappedGetters, (fn, key) => { computed[key] = () => fn(store) Object.defineProperty(store.getters, key, { get: () => store._vm[key], enumerable: true // for local getters }) }) const silent = Vue.config.silent Vue.config.silent = true store._vm = new Vue({ data: { $$state: state }, computed }) Vue.config.silent = silent if (oldVm) { if (hot) { store._withCommit(() => { oldVm._data.$$state = null }) } Vue.nextTick(() => oldVm.$destroy()) } }
resetStoreVM这个方法主要是重置一个私有的 _vm对象,它是一个 Vue 的实例。传入的有store表示当前Store实例,state表示状态,hot就不用再说了。对store.getters进行初始化,获取store._wrappedGetters而且用计算属性的方法存储了store.getters,因此store.getters是Vue的计算属性。使用defineProperty方法给store.getters添加属性,当咱们使用store.getters[key]实际上获取的是store._vm[key]。而后用一个Vue的实例data来存储state 树,这里使用slient=true,是为了取消提醒和警告,在这里将一下slient的做用,silent表示静默模式(网上找的定义),就是若是开启就没有提醒和警告。后面的代码表示若是oldVm存在而且hot为true时,经过_witchCommit修改$$state的值,而且摧毁oldVm对象
get state () { return this._vm._data.$$state }
由于咱们将state信息挂载到_vm这个Vue实例对象上,因此在获取state的时候,实际访问的是this._vm._data.$$state。
watch (getter, cb, options) { return this._watcherVM.$watch(() => getter(this.state, this.getters), cb, options) }
在wacher函数中,咱们先前提到的this._watcherVM就起到了做用,在这里咱们利用了this._watcherVM中$watch方法进行了数据的检测。watch 做用是响应式的监测一个 getter 方法的返回值,当值改变时调用回调。getter必须是一个函数,若是返回的值发生了变化,那么就调用cb回调函数。
咱们继续来将
registerModule (path, rawModule, options = {}) { if (typeof path === 'string') path = [path] this._modules.register(path, rawModule) installModule(this, this.state, path, this._modules.get(path), options.preserveState) resetStoreVM(this, this.state) }
没什么难度就是,注册一个动态模块,首先判断path,将其转换为数组,register()函数的做用就是初始化一个模块,安装模块,重置Store._vm。
由于文章写太长了,估计没人看,因此本文对代码进行了简化,有些地方没写出来,其余都是比较简单的地方。有兴趣的能够本身去看一下,最后补一张图,结合起来看源码更能事半功倍。这张图要我本身经过源码画出来,再给我看几天我也是画不出来的,因此还须要努力啊