Vuex
是作为vue
的一个插件而存在的,它只能应用在vue
中,是因为其高度依赖vue
中的computed
依赖检测系统以及其插件系统。
在vue
中,每一个插件都需要一个公开的install
方法,在vuex
中,它的install
方法中调用了applyMixin
方法。这个方法的主要作用是在所有组件的beforeCreate
生命周期中注入this.$store
这样一个对象。源码如下:
// src/store.js
export function install (_Vue) {
if (Vue && _Vue === Vue) {
if (__DEV__) {
console.error(
'[vuex] already installed. Vue.use(Vuex) should be called only once.'
)
}
return
}
Vue = _Vue
applyMixin(Vue)
}
// src/mixin.js
// 对应的applyMinix方法
export default function (Vue) {
const version = Number(Vue.version.split('.')[0])
if (version >= 2) {
// vue版本大于2使用混入的方式,在每个组件的beforeCreate生命周期中混入vuexInit函数
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) {
// 将参数中的store对象,赋值给被混入的组件的this.$store属性
this.$store = typeof options.store === 'function'
? options.store()
: options.store
} else if (options.parent && options.parent.$store) {
this.$store = options.parent.$store
}
}
}
我们在使用vuex
时的写法如下:
export default new Vuex.Store({
state: {
count: 0,
name: 'index'
},
mutations: {
},
actions: {
},
modules: { moduleA, moduleB },
getters: {
}
})
那么Vuex.Store
具体是什么呢,我们来看看它的构造函数
// src/store.js
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 (__DEV__) {
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()
this._makeLocalGettersCache = Object.create(null)
// 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)
// 核心代码,vuex中的state与getter响应式的实现操作都在该函数中
resetStoreVM(this, state)
// apply plugins
plugins.forEach(plugin => plugin(this))
const useDevtools = options.devtools !== undefined ? options.devtools : Vue.config.devtools
if (useDevtools) {
devtoolPlugin(this)
}
}
除了一些初始化外,最重要的一行代码是resetStoreVM(this, state)
// src/store.js
function resetStoreVM (store, state, hot) {
const oldVm = store._vm
// bind store public getters
store.getters = {}
// reset local getters cache
store._makeLocalGettersCache = Object.create(null)
const wrappedGetters = store._wrappedGetters
const computed = {}
// 将getter收集入computed中
forEachValue(wrappedGetters, (fn, key) => {
// use computed to leverage its lazy-caching mechanism
// direct inline function use will lead to closure preserving oldVm.
// using partial to return function with only arguments preserved in closure environment.
computed[key] = partial(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
// 实例化一个vue实例,将该实例作为一个隐形组件处理,将state作为data中的值,getter作为计算属性。利用data和计算属性的响应式机制实现vuex数据的响应式
store._vm = new Vue({
data: {
$$state: state
},
computed
})
}
核心部分的代码意图其实就是将我们传入的state
作为一个隐藏的vue
组件的data
。所以每次我们对其数据进行操作,本质上其实是修改这个组件的data
。这里的computed
则是vuex
中的getter
实际上也是走的computed
机制。
vuex
注入流程大致如下
vuex
的响应原理如下