src/index.js
vuex的目录结构非常简单,打开src文件夹,肯定先看index.js文件
js
import { Store, createStore } from './store'
import { storeKey, useStore } from './injectKey'
import { mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers } from './helpers'
import { createLogger } from './plugins/logger'
export default {
version: '__VERSION__',
Store,
storeKey,
createStore,
useStore,
mapState,
mapMutations,
mapGetters,
mapActions,
createNamespacedHelpers,
createLogger
}
export {
Store,
storeKey,
createStore,
useStore,
mapState,
mapMutations,
mapGetters,
mapActions,
createNamespacedHelpers,
createLogger
}
import { Store, createStore } from './store'
import { storeKey, useStore } from './injectKey'
import { mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers } from './helpers'
import { createLogger } from './plugins/logger'
export default {
version: '__VERSION__',
Store,
storeKey,
createStore,
useStore,
mapState,
mapMutations,
mapGetters,
mapActions,
createNamespacedHelpers,
createLogger
}
export {
Store,
storeKey,
createStore,
useStore,
mapState,
mapMutations,
mapGetters,
mapActions,
createNamespacedHelpers,
createLogger
}
createStore
打开src\store.js
,首先看到的是createStore
函数,createStore
函数是vue3中使用vuex的语法,可以看出来其实它就是new了一个Store
js
export function createStore (options) {
return new Store(options)
}
export function createStore (options) {
return new Store(options)
}
再往下看,Store是vuex的核心类,总共也就两百多行,一行一行看也很快
js
export class Store {
constructor (options = {}) {
if (__DEV__) {
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,
devtools
} = 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._makeLocalGettersCache = Object.create(null)
// EffectScope instance. when registering new getters, we wrap them inside
// EffectScope so that getters (computed) would not be destroyed on
// component unmount.
this._scope = null
this._devtools = devtools
// 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 state, which is responsible for the reactivity
// (also registers _wrappedGetters as computed properties)
resetStoreState(this, state)
// apply plugins
plugins.forEach(plugin => plugin(this))
}
install (app, injectKey) {
app.provide(injectKey || storeKey, this)
app.config.globalProperties.$store = this
const useDevtools = this._devtools !== undefined
? this._devtools
: __DEV__ || __VUE_PROD_DEVTOOLS__
if (useDevtools) {
addDevtools(app, this)
}
}
get state () {
return this._state.data
}
set state (v) {
if (__DEV__) {
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 (__DEV__) {
console.error(`[vuex] unknown mutation type: ${type}`)
}
return
}
this._withCommit(() => {
entry.forEach(function commitIterator (handler) {
handler(payload)
})
})
this._subscribers
.slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
.forEach(sub => sub(mutation, this.state))
if (
__DEV__ &&
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 (__DEV__) {
console.error(`[vuex] unknown action type: ${type}`)
}
return
}
try {
this._actionSubscribers
.slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
.filter(sub => sub.before)
.forEach(sub => sub.before(action, this.state))
} catch (e) {
if (__DEV__) {
console.warn(`[vuex] error in before action subscribers: `)
console.error(e)
}
}
const result = entry.length > 1
? Promise.all(entry.map(handler => handler(payload)))
: entry[0](payload)
return new Promise((resolve, reject) => {
result.then(res => {
try {
this._actionSubscribers
.filter(sub => sub.after)
.forEach(sub => sub.after(action, this.state))
} catch (e) {
if (__DEV__) {
console.warn(`[vuex] error in after action subscribers: `)
console.error(e)
}
}
resolve(res)
}, error => {
try {
this._actionSubscribers
.filter(sub => sub.error)
.forEach(sub => sub.error(action, this.state, error))
} catch (e) {
if (__DEV__) {
console.warn(`[vuex] error in error action subscribers: `)
console.error(e)
}
}
reject(error)
})
})
}
subscribe (fn, options) {
return genericSubscribe(fn, this._subscribers, options)
}
subscribeAction (fn, options) {
const subs = typeof fn === 'function' ? { before: fn } : fn
return genericSubscribe(subs, this._actionSubscribers, options)
}
watch (getter, cb, options) {
if (__DEV__) {
assert(typeof getter === 'function', `store.watch only accepts a function.`)
}
return watch(() => getter(this.state, this.getters), cb, Object.assign({}, options))
}
replaceState (state) {
this._withCommit(() => {
this._state.data = state
})
}
registerModule (path, rawModule, options = {}) {
if (typeof path === 'string') path = [path]
if (__DEV__) {
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...
resetStoreState(this, this.state)
}
unregisterModule (path) {
if (typeof path === 'string') path = [path]
if (__DEV__) {
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))
delete parentState[path[path.length - 1]]
})
resetStore(this)
}
hasModule (path) {
if (typeof path === 'string') path = [path]
if (__DEV__) {
assert(Array.isArray(path), `module path must be a string or an Array.`)
}
return this._modules.isRegistered(path)
}
hotUpdate (newOptions) {
this._modules.update(newOptions)
resetStore(this, true)
}
_withCommit (fn) {
const committing = this._committing
this._committing = true
fn()
this._committing = committing
}
}
export class Store {
constructor (options = {}) {
if (__DEV__) {
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,
devtools
} = 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._makeLocalGettersCache = Object.create(null)
// EffectScope instance. when registering new getters, we wrap them inside
// EffectScope so that getters (computed) would not be destroyed on
// component unmount.
this._scope = null
this._devtools = devtools
// 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 state, which is responsible for the reactivity
// (also registers _wrappedGetters as computed properties)
resetStoreState(this, state)
// apply plugins
plugins.forEach(plugin => plugin(this))
}
install (app, injectKey) {
app.provide(injectKey || storeKey, this)
app.config.globalProperties.$store = this
const useDevtools = this._devtools !== undefined
? this._devtools
: __DEV__ || __VUE_PROD_DEVTOOLS__
if (useDevtools) {
addDevtools(app, this)
}
}
get state () {
return this._state.data
}
set state (v) {
if (__DEV__) {
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 (__DEV__) {
console.error(`[vuex] unknown mutation type: ${type}`)
}
return
}
this._withCommit(() => {
entry.forEach(function commitIterator (handler) {
handler(payload)
})
})
this._subscribers
.slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
.forEach(sub => sub(mutation, this.state))
if (
__DEV__ &&
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 (__DEV__) {
console.error(`[vuex] unknown action type: ${type}`)
}
return
}
try {
this._actionSubscribers
.slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
.filter(sub => sub.before)
.forEach(sub => sub.before(action, this.state))
} catch (e) {
if (__DEV__) {
console.warn(`[vuex] error in before action subscribers: `)
console.error(e)
}
}
const result = entry.length > 1
? Promise.all(entry.map(handler => handler(payload)))
: entry[0](payload)
return new Promise((resolve, reject) => {
result.then(res => {
try {
this._actionSubscribers
.filter(sub => sub.after)
.forEach(sub => sub.after(action, this.state))
} catch (e) {
if (__DEV__) {
console.warn(`[vuex] error in after action subscribers: `)
console.error(e)
}
}
resolve(res)
}, error => {
try {
this._actionSubscribers
.filter(sub => sub.error)
.forEach(sub => sub.error(action, this.state, error))
} catch (e) {
if (__DEV__) {
console.warn(`[vuex] error in error action subscribers: `)
console.error(e)
}
}
reject(error)
})
})
}
subscribe (fn, options) {
return genericSubscribe(fn, this._subscribers, options)
}
subscribeAction (fn, options) {
const subs = typeof fn === 'function' ? { before: fn } : fn
return genericSubscribe(subs, this._actionSubscribers, options)
}
watch (getter, cb, options) {
if (__DEV__) {
assert(typeof getter === 'function', `store.watch only accepts a function.`)
}
return watch(() => getter(this.state, this.getters), cb, Object.assign({}, options))
}
replaceState (state) {
this._withCommit(() => {
this._state.data = state
})
}
registerModule (path, rawModule, options = {}) {
if (typeof path === 'string') path = [path]
if (__DEV__) {
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...
resetStoreState(this, this.state)
}
unregisterModule (path) {
if (typeof path === 'string') path = [path]
if (__DEV__) {
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))
delete parentState[path[path.length - 1]]
})
resetStore(this)
}
hasModule (path) {
if (typeof path === 'string') path = [path]
if (__DEV__) {
assert(Array.isArray(path), `module path must be a string or an Array.`)
}
return this._modules.isRegistered(path)
}
hotUpdate (newOptions) {
this._modules.update(newOptions)
resetStore(this, true)
}
_withCommit (fn) {
const committing = this._committing
this._committing = true
fn()
this._committing = committing
}
}
useStore
打开src\injectKey.js
,看看useStore
如何实现的,可以看到代码非常简单,不传入key的话,就默认inject
store这个key值
provide
和 inject
是vue依赖注入机制的语法。一个父组件使用provide
提供依赖,其所有的后代组件,无论层级有多深,都可以inject
获取由父组件provide
提供的依赖
依赖注入 https://cn.vuejs.org/guide/components/provide-inject.html
js
import { inject } from 'vue'
export const storeKey = 'store'
export function useStore (key = null) {
return inject(key !== null ? key : storeKey)
}
import { inject } from 'vue'
export const storeKey = 'store'
export function useStore (key = null) {
return inject(key !== null ? key : storeKey)
}
有inject
说明有provide
,他就在上面的src\store.js
代码中,install函数第一行就provide
了store
js
install (app, injectKey) {
app.provide(injectKey || storeKey, this)
...
}
install (app, injectKey) {
app.provide(injectKey || storeKey, this)
...
}
mapState
打开src\helpers.js
,看看辅助函数们是怎么实现的
js
/**
* Reduce the code which written in Vue.js for getting the state.
* @param {String} [namespace] - Module's namespace
* @param {Object|Array} states # Object's item can be a function which accept state and getters for param, you can do something for state and getters in it.
* @param {Object}
*/
export const mapState = normalizeNamespace((namespace, states) => {
const res = {}
if (__DEV__ && !isValidMap(states)) {
console.error("[vuex] mapState: mapper parameter must be either an Array or an Object")
}
normalizeMap(states).forEach(({ key, val }) => {
res[key] = function mappedState() {
let state = this.$store.state
let getters = this.$store.getters
if (namespace) {
const module = getModuleByNamespace(this.$store, "mapState", namespace)
if (!module) {
return
}
state = module.context.state
getters = module.context.getters
}
return typeof val === "function" ? val.call(this, state, getters) : state[val]
}
// mark vuex getter for devtools
res[key].vuex = true
})
return res
})
/**
* Reduce the code which written in Vue.js for getting the state.
* @param {String} [namespace] - Module's namespace
* @param {Object|Array} states # Object's item can be a function which accept state and getters for param, you can do something for state and getters in it.
* @param {Object}
*/
export const mapState = normalizeNamespace((namespace, states) => {
const res = {}
if (__DEV__ && !isValidMap(states)) {
console.error("[vuex] mapState: mapper parameter must be either an Array or an Object")
}
normalizeMap(states).forEach(({ key, val }) => {
res[key] = function mappedState() {
let state = this.$store.state
let getters = this.$store.getters
if (namespace) {
const module = getModuleByNamespace(this.$store, "mapState", namespace)
if (!module) {
return
}
state = module.context.state
getters = module.context.getters
}
return typeof val === "function" ? val.call(this, state, getters) : state[val]
}
// mark vuex getter for devtools
res[key].vuex = true
})
return res
})
mapState最终返回一个对象,然后可以通过扩展运算符来合并对象中的属性
js
const a = {a:1}
const b = {b:2}
console.log({...a, ...b}) // { a: 1, b: 2 }
const a = {a:1}
const b = {b:2}
console.log({...a, ...b}) // { a: 1, b: 2 }
normalizeMap
这个函数用于将对象和数组,转换成统一的数组格式 [{ key: 1, val: 1 }, { key: 2, val: 2 }, { key: 3, val: 3 }]
,因为mapState这些函数可以传对象也可以传数组
js
/**
* Normalize the map
* normalizeMap([1, 2, 3]) => [ { key: 1, val: 1 }, { key: 2, val: 2 }, { key: 3, val: 3 } ]
* normalizeMap({a: 1, b: 2, c: 3}) => [ { key: 'a', val: 1 }, { key: 'b', val: 2 }, { key: 'c', val: 3 } ]
* @param {Array|Object} map
* @return {Object}
*/
function normalizeMap(map) {
if (!isValidMap(map)) {
return []
}
return Array.isArray(map) ? map.map(key => ({ key, val: key })) : Object.keys(map).map(key => ({ key, val: map[key] }))
}
/**
* Validate whether given map is valid or not
* @param {*} map
* @return {Boolean}
*/
function isValidMap(map) {
// 判断是数组或是对象
return Array.isArray(map) || isObject(map)
}
/**
* Normalize the map
* normalizeMap([1, 2, 3]) => [ { key: 1, val: 1 }, { key: 2, val: 2 }, { key: 3, val: 3 } ]
* normalizeMap({a: 1, b: 2, c: 3}) => [ { key: 'a', val: 1 }, { key: 'b', val: 2 }, { key: 'c', val: 3 } ]
* @param {Array|Object} map
* @return {Object}
*/
function normalizeMap(map) {
if (!isValidMap(map)) {
return []
}
return Array.isArray(map) ? map.map(key => ({ key, val: key })) : Object.keys(map).map(key => ({ key, val: map[key] }))
}
/**
* Validate whether given map is valid or not
* @param {*} map
* @return {Boolean}
*/
function isValidMap(map) {
// 判断是数组或是对象
return Array.isArray(map) || isObject(map)
}
createNamespacedHelpers
- 创建基于某个命名空间辅助函数
- 返回一个对象,对象中有新的绑定在(给定命名空间值上)的组件绑定辅助函数
js
import { createNamespacedHelpers } from 'vuex'
const { mapState, mapActions } = createNamespacedHelpers('main')
export default {
computed: {
//在`main`中查找
...mapState({
a: state => state.a
})
},
methods: {
//在`main`中查找
...mapActions([
'fn'
])
}
}
import { createNamespacedHelpers } from 'vuex'
const { mapState, mapActions } = createNamespacedHelpers('main')
export default {
computed: {
//在`main`中查找
...mapState({
a: state => state.a
})
},
methods: {
//在`main`中查找
...mapActions([
'fn'
])
}
}
源码
bind第一个参数null,表示不改变指向,第二个参数传入命名空间,返回相应命名空间对象
js
/**
* Rebinding namespace param for mapXXX function in special scoped, and return them by simple object
* @param {String} namespace
* @return {Object}
*/
export const createNamespacedHelpers = namespace => ({
mapState: mapState.bind(null, namespace),
mapGetters: mapGetters.bind(null, namespace),
mapMutations: mapMutations.bind(null, namespace),
mapActions: mapActions.bind(null, namespace),
})
/**
* Rebinding namespace param for mapXXX function in special scoped, and return them by simple object
* @param {String} namespace
* @return {Object}
*/
export const createNamespacedHelpers = namespace => ({
mapState: mapState.bind(null, namespace),
mapGetters: mapGetters.bind(null, namespace),
mapMutations: mapMutations.bind(null, namespace),
mapActions: mapActions.bind(null, namespace),
})