最終也是最重要的store.js
,該文件主要涉及的內容以下:vue
let Vue
上面這些和這個Vue
都在同一個做用域installreact
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) }
解析:vuex
beforeCreate
期間給全部組件建立$store
Vue.use(Vuex)
unifyObjectStyle數組
function unifyObjectStyle (type, payload, options) { if (isObject(type) && type.type) { options = payload payload = type type = type.type } if (process.env.NODE_ENV !== 'production') { assert(typeof type === 'string', `expects string as the type, but found ${typeof type}.`) } return { type, payload, options } }
解析:緩存
dispatch('pushTab', payload, options)
和 dispatch({ type: 'pushTab', payload }, options)
這種狀況getNestedState數據結構
function getNestedState (state, path) { return path.length ? path.reduce((state, key) => state[key], state) : state }
解析:app
[]
爲非嵌套或根模塊,{ shop: { card: { item: 1 } } }
的path爲['shop', 'card', 'item']
item
[{ name: 'getMe' }, { name: 'notMe' }]
,要獲取'getMe'
那麼const path = [0, 'name']
enableStrictMode異步
function enableStrictMode (store) { store._vm.$watch(function () { return this._data.$$state }, () => { if (process.env.NODE_ENV !== 'production') { assert(store._committing, `do not mutate vuex store state outside mutation handlers.`) } }, { deep: true, sync: true }) }
解析:ide
$watch
就是Vue.$watch
,主要功能是:若是啓動了嚴格模式,監控數據狀態的改變是否是經過commit
改變的。commit
改變狀態的,在開發模式下會提示。VueX
的狀態是存在於一個Vue實例中的_data.$$state
,Store._vm
能夠解釋VueX
爲何叫VueX
VueX
是Vue的一個實例
genericSubscribe函數
function genericSubscribe (fn, subs) { if (subs.indexOf(fn) < 0) { subs.push(fn) } return () => { const i = subs.indexOf(fn) if (i > -1) { subs.splice(i, 1) } } }
解析:
subs
是觀察者,subs
關聯到某個狀態,那個狀態改變,會遍歷subs
調用裏面的函數。const unsubscribe = genericSubscribe(fn, subs)
,調用unsubscribe()
就能夠取消訂閱幾個輔助函數和狀態相關
context
let Vue // bind on install export class 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) } } 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) { // 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) } 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) // reset store to update getters... resetStoreVM(this, this.state) } unregisterModule (path) { 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.`) } this._modules.unregister(path) this._withCommit(() => { const parentState = getNestedState(this.state, path.slice(0, -1)) Vue.delete(parentState, path[path.length - 1]) }) resetStore(this) } hotUpdate (newOptions) { this._modules.update(newOptions) resetStore(this, true) } _withCommit (fn) { const committing = this._committing this._committing = true fn() this._committing = committing } }
解析:
install(window.Vue)
若是是Vue2,經過mixins的方式添加beforeCreate
鉤子函數,把store傳給任何繼承這個Vue的實例(組件),因此全部組件都擁有一個$store的屬性。也即每一個組件都能拿到store。
第二個斷言判斷是確保Vue在當前環境中,且須要Promise,強制Store做爲構造函數。
可配置項有兩個值plugins
和是否使用strict
嚴格模式(只能經過commit改變狀態),plugins
通常用於開發調試
將全局(非模塊內)的dispatch和commit
的this
綁定到store
Store類的靜態屬性
_committing
:記錄當前是否commit中_actions
:記錄全部action的字段,有模塊做用域的用'/'分隔_actionSubscribers
:全局的dispatch後遍歷調用數組中的函數_mutations
:和actions同樣_wrappedGetters
:無論是使用了模塊仍是全局的getter都存在於此_modules
:全局模塊_modulesNamespaceMap
:全部模塊全存於此,可經過namespace取出想要的模塊_subscribers
:全局的commit調用以後,遍歷調用數組中的函數_watcherVM
:Vue實例用於watch()
函數strict
:是否使用嚴格模式,使用那麼改變狀態只能經過commit來改變狀態state
:Store的全部state包含模塊的_vm
:Vue的實例,VueX
真正狀態是存於這裏Store._vm._data.$$state
installModule
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) }) }
this._wrappedGetters
中。_modulesNamespaceMap
存放全部模塊,能夠經過namespaced來獲取模塊(數據結構:哈希表)Vue.set(parentState, moduleName, module.state)
因爲VueX
是Vue的實例,Vue設置的狀態,它的實例(VueX)能夠繼承context
makeLocalContext
/** * make localized dispatch, commit, getters and state * if there is no namespace, just use root ones */ function makeLocalContext (store, namespace, path) { const noNamespace = namespace === '' const local = { dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => { const args = unifyObjectStyle(_type, _payload, _options) const { payload, options } = args let { type } = args if (!options || !options.root) { type = namespace + type if (process.env.NODE_ENV !== 'production' && !store._actions[type]) { console.error(`[vuex] unknown local action type: ${args.type}, global type: ${type}`) return } } return store.dispatch(type, payload) }, commit: noNamespace ? store.commit : (_type, _payload, _options) => { const args = unifyObjectStyle(_type, _payload, _options) const { payload, options } = args let { type } = args if (!options || !options.root) { type = namespace + type if (process.env.NODE_ENV !== 'production' && !store._mutations[type]) { console.error(`[vuex] unknown local mutation type: ${args.type}, global type: ${type}`) return } } store.commit(type, payload, options) } } // getters and state object must be gotten lazily // because they will be changed by vm update Object.defineProperties(local, { getters: { get: noNamespace ? () => store.getters : () => makeLocalGetters(store, namespace) }, state: { get: () => getNestedState(store.state, path) } }) return local }
context
() => store.getters
,只有調用這個函數才獲取到全部getters,結合get
就是隻有引用這個屬性纔會獲取全部getters() => fn()
要留意的是:是fn()
而不是fn
,fn()
纔是要執行的命令
makeLocalGetters
function makeLocalGetters (store, namespace) { const gettersProxy = {} const splitPos = namespace.length Object.keys(store.getters).forEach(type => { // skip if the target getter is not match this namespace if (type.slice(0, splitPos) !== namespace) return // extract local getter type const localType = type.slice(splitPos) // Add a port to the getters proxy. // Define as getter property because // we do not want to evaluate the getters in this time. Object.defineProperty(gettersProxy, localType, { get: () => store.getters[type], enumerable: true }) }) return gettersProxy }
resetStoreVM
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
的值_vm
的$$state保存的是store.state_vm
的computed
是store._wrappedGetters
的值VueX
的狀態是存在於一個Vue實例
的$data中的VueX
內部的_vm
的,也能夠認爲就是gettersregisterMutation
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._mutations[type]
,type就是命名空間,按命名空間來存放mutationsregisterAction
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 } }) }
store._actions[type]
存儲結構和_mutations
的同樣,按命名空間來存儲action{ dispatch, commit, getters, state, rootGetters, rootState }
{ dispatch, commit, getters, state }
是局部local的registerGetter
function registerGetter (store, type, rawGetter, local) { if (store._wrappedGetters[type]) { if (process.env.NODE_ENV !== 'production') { console.error(`[vuex] duplicate getter key: ${type}`) } return } store._wrappedGetters[type] = function wrappedGetter (store) { return rawGetter( local.state, // local state local.getters, // local getters store.state, // root state store.getters // root getters ) } }
store._wrappedGetters[type]
因爲全部getters放在一個對象,結構和actions、mutations的結構就不同了,就是一個對象resetStore
function resetStore (store, hot) { store._actions = Object.create(null) store._mutations = Object.create(null) store._wrappedGetters = Object.create(null) store._modulesNamespaceMap = Object.create(null) const state = store.state // init all modules installModule(store, state, [], store._modules.root, true) // reset vm resetStoreVM(store, state, hot) }
installModule
和resetStoreVM
state()
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.`) } }
store._vm._data.$$state
中獲取replaceState
replaceState (state) { this._withCommit(() => { this._vm._data.$$state = state }) }
store._vm._data.$$state
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
存儲的是全部的type
(包括模塊的),這個commit能夠commit('shop/card')
只要命名路徑對_subscribers
,遍歷該數組調用回調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
,和commit
同樣,_actions
存放的是全部的action的type
(包括模塊的)_actionSubscribers
中訂閱的回調函數_actions[type]
是一個數組,能夠一個type
不一樣處理,也即_actions[type] = [fn1, fn2, fn3, ...]
Promise.all
等到全部函數都調用完才統一處理(支持異步)subscribe
subscribe (fn) { return genericSubscribe(fn, this._subscribers) }
commit
,只要調用全局的commit
就調用,模塊內的context.commit
也是會調用所有的commit
subscribeAction
subscribeAction (fn) { return genericSubscribe(fn, this._actionSubscribers) }
action
,只要調用全局的action
就調用,模塊內的context.dispatch
也是會調用所有的dispatch
watch
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) }
Vue
的$watch
,觀察想要監控的狀態registerModule
unregisterModule
hotUpdate
registerModule
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) // reset store to update getters... resetStoreVM(this, this.state) }
Store
和store._vm
installModule
和 resetStoreVM
這兩個函數很總要,涉及到性能,重點、重點unregisterModule
unregisterModule (path) { 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.`) } this._modules.unregister(path) this._withCommit(() => { const parentState = getNestedState(this.state, path.slice(0, -1)) Vue.delete(parentState, path[path.length - 1]) }) resetStore(this) }
Vue.delete(parentState, path[path.length - 1])
和resetStore(this)
Store
和_vm
hotUpdate
hotUpdate (newOptions) { this._modules.update(newOptions) resetStore(this, true) }
resetStore(this, true)
,仍是會從新建立Store
和_vm
很重要的3個函數
resetStore
:包含resetStoreVM
和installModule
resetStoreVM
installModule