# 首先 clone 一下项目
git clone https://github.com/vuejs/vuex.git
# 然后进入源码目录
cd ./vuex/src
# 源码目录结构
.
├── helpers.js // 提供 action、 mutations 以及 getters 的查找 API
├── index.esm.js
├── index.js // 源码主入口文件
├── mixin.js // 提供 store 在 Vue 实例上的装载注入
├── module // 提供 module 对象与 module 对象树的创建功能
│ ├── module-collection.js
│ └── module.js
├── plugins // 提供开发辅助插件
│ ├── devtool.js
│ └── logger.js
├── store.js
└── util.js // 提供工具方法
// 源码的入口文件,提供 store 的各个 module 的构建安装
import { Store, install } from './store'
import { mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers } from './helpers'
// 从这里就可以看出,我们可以使用 vuex 的什么方法与属性,如最基本的 Store
// 供给 Vue.use() 全局挂载的 install
// 展开内容的 mapState, mapMutations, mapGettrs, mapActions
// 创建基于命名空间的组件绑定辅助函数 createNamespacedHelpers
export default {
Store,
install,
version: '__VERSION__',
mapState,
mapMutations,
mapGetters,
mapActions,
createNamespacedHelpers
}
/**
* mapState 方法,方便快速取值的,竟然大家都看 vuex 源码了,它肯定很熟悉了吧
*/
export const mapState = normalizeNamespace((namespace, states) => {
const res = {}
// 把要取的内容序列化成指定的格式,然后遍历执行回调,并赋值给 res[key]
normalizeMap(states).forEach(({ key, val }) => {
res[key] = function mappedState () {
let state = this.$store.state
let getters = this.$store.getters
// 如果是存在命名空间,那就表示使用了模块,在模块内找内容,否则在全局 $store 中查找内容
if (namespace) {
// 根据模块名称在顶层的 $store 中找到模块的内容
// 如果不存在就直接返回
// 如果存在取出模块的 state, getters 存储在变量 state, getters 中
const module = getModuleByNamespace(this.$store, 'mapState', namespace)
if (!module) {
return
}
state = module.context.state
getters = module.context.getters
}
// 如果要取的是 getters 中的内容,那就表示它调用的是一个函数,直接函数并返回结果,否则就是取 state 中的内容,直接返回内容
return typeof val === 'function'
? val.call(this, state, getters)
: state[val]
}
// 标记在 devtools 中的 vuex key,将其 flag 调为 true
res[key].vuex = true
})
// 返回 res 的内容,因为 res 是引用类型,虽然声明的时候使用了 const 但是还是可以为其添加内容的,毕竟我们没有改它的对象地址
return res
})
/**
* mapMutations 方法
*/
export const mapMutations = normalizeNamespace((namespace, mutations) => {
const res = {}
// 序列化好参数,将其转换为指定格式然后 forEach 遍历
normalizeMap(mutations).forEach(({ key, val }) => {
res[key] = function mappedMutation (...args) {
// 从根目录的 $store 上拿到 commit 存储到 commit 上
let commit = this.$store.commit
// 处理命名空间的情况,如果存在命名空间,则调整参数
if (namespace) {
const module = getModuleByNamespace(this.$store, 'mapMutations', namespace)
if (!module) {
return
}
commit = module.context.commit
}
// 如果传入的 val 是一个函数,那么执行这个函数,并返回结果
// 否则就执行 commit
// 这里使用的是 apply 是因为 apply 最合适处理传入不确定数量的参数的情况
return typeof val === 'function'
? val.apply(this, [commit].concat(args))
: commit.apply(this.$store, [val].concat(args))
}
})
return res
})
/**
* mapGetters 方法
*/
export const mapGetters = normalizeNamespace((namespace, getters) => {
const res = {}
// 把所有传入的参数序列化,然后调用 forEach 进行处理
normalizeMap(getters).forEach(({ key, val }) => {
// 如果有命名空间,那么会自动加上,如果没有命名空间,会加上 '' ,这样其实没有改变
val = namespace + val
res[key] = function mappedGetter () {
// 如果命名空间存在,但是没有找到,这个时候就直接返回一个 undefined
if (namespace && !getModuleByNamespace(this.$store, 'mapGetters', namespace)) {
return
}
// 如果现在不是线上环境且 val 并不在 $store 的列表中,那么就报错
if (process.env.NODE_ENV !== 'production' && !(val in this.$store.getters)) {
console.error(`[vuex] unknown getter: ${val}`)
return
}
// 错误情况全部排除了,这个时候我们就可以返回指定的 getters 的计算内容了
return this.$store.getters[val]
}
// 标记在 devtools 中的 vuex key,将其 flag 调为 true
res[key].vuex = true
})
return res
})
/**
* mapActions 方法,这里就不做解释了,它和 mapMutations 很像,区别是执行的是 commit, dispatch 而已
* 和 mapMutations 的主要区别是它可以执行异步方法
* 其实 mapMutations 也可以执行异步方法,但是它为了更好的实现编程思想造成的
*/
export const mapActions = normalizeNamespace((namespace, actions) => {
const res = {}
normalizeMap(actions).forEach(({ key, val }) => {
res[key] = function mappedAction (...args) {
let dispatch = this.$store.dispatch
if (namespace) {
const module = getModuleByNamespace(this.$store, 'mapActions', namespace)
if (!module) {
return
}
dispatch = module.context.dispatch
}
return typeof val === 'function'
? val.apply(this, [dispatch].concat(args))
: dispatch.apply(this.$store, [val].concat(args))
}
})
return res
})
/**
* createNamespacedHelpers 创建基于命名空间的组件绑定辅助函数
* 用于快速为命名空间生成 mapState, mapGetters, mapMutations, mapActions 等属性
*/
export const createNamespacedHelpers = (namespace) => ({
mapState: mapState.bind(null, namespace),
mapGetters: mapGetters.bind(null, namespace),
mapMutations: mapMutations.bind(null, namespace),
mapActions: mapActions.bind(null, namespace)
})
/**
* 把内容序列化成一个 Map 的形式,返回一个数组,方便调用,传入参数只能是数组或者对象
* 序列化成 { key: keyName, val: value} 的形式在数组中,这样使用 filter, forEach, every, any 等方法的时候就比较方便了
*/
function normalizeMap (map) {
// 这里是通过检查是否是数组来判断能不是能进行 map 操作来直接转换
// 如果是数组,那就就用 map 方法进行直接转换
// 如果不是数组,那么就是对象,就将对象的属性名使用 Object.keys() 方法来将其属性名整合成一个数组,再利用数组的 map 方法转换
return Array.isArray(map)
? map.map(key => ({ key, val: key }))
: Object.keys(map).map(key => ({ key, val: map[key] }))
}
/**
* 这里一个函数式编程的经典实例,首先传入回调,并返回一个函数
* 返回的函数我们可以存在一个新的变量中,然后执行,以后就只用传入 namespace 与 map 就可以了, fn 已经常驻内存中
*/
function normalizeNamespace (fn) {
return (namespace, map) => {
// 这里是调节参数,是用于处理没有传入 namespace 的情况的
if (typeof namespace !== 'string') {
map = namespace
namespace = ''
} else if (namespace.charAt(namespace.length - 1) !== '/') {
// 如果 namespace 最后一位不是 '/' 那么为了以后的处理方便,添加上 '/'
namespace += '/'
}
// 返回执行回调的结果,注:这里调用 normalizeNamespace 返回的值才返回的,此时 fn 已经常驻在内存中了
return fn(namespace, map)
}
}
/**
* 拿到模块名拿到对应的模块
*/
function getModuleByNamespace (store, helper, namespace) {
// 从 store 的 map 中找到对应的模块存储在 module 中,如果有内容就是一个对象,如果没有内容,那么则是 undefined
const module = store._modulesNamespaceMap[namespace]
// 生产环境下不报错,如果是开发环境,且 module 不存在的话,那还是会报错的
if (process.env.NODE_ENV !== 'production' && !module) {
console.error(`[vuex] module namespace not found in ${helper}(): ${namespace}`)
}
// 返回找到的结果
return module
}
export default function (Vue) {
// 取大版本号 一般 Vue 的版本号者是 X.X.X 形式的,这样就可以取到第一位的大版本号
const version = Number(Vue.version.split('.')[0])
// 如果大版本号大于等于 2 ,那就表示 Vue 拥有了 mixin 方法
// 这样我们就可以直接调用它,刚 vuexInit 添加到 beforeCreate 钩子函数中
if (version >= 2) {
Vue.mixin({ beforeCreate: vuexInit })
} else {
// 如果使用的是旧版本的 Vue 那么没有 mixin ,要使用 _init 方法来调用
// 先把 Vue.prototype._init 存储在常量中,之所以使用常量,就是为了防止不小心修改
// 然后扩充 Vue.prototype._init 方法,在 options 中添加属性内容 vuexInit
const _init = Vue.prototype._init
Vue.prototype._init = function (options = {}) {
// 如果 options.init 之前有参数,那就就将参数拼接起来来
// 如果没有参数,那么就将其当今参数传入
options.init = options.init
? [vuexInit].concat(options.init)
: vuexInit
// 然后执行原来的 Vue.prototype._init 方法,从可以我们可以看出
// 这次的扩充函数其实主要就是添加了参数设置,在执行步骤上没有任何的改变
_init.call(this, options)
}
}
/**
* 内部函数,就是使 store 在 Vue 上 mixin 的方法
*/
function vuexInit () {
// 先保存系统参数,存为常量,防止误修改
const options = this.$options
// 如果系统参数中存在 store 那就检查其是否为 store 对象的实例
// 如果是的话那就将其挂载在 this.$store 上
// 如果不是的话,那么就实例化一个 store 对象并挂载在 this.$store 上
if (options.store) {
this.$store = typeof options.store === 'function'
? options.store()
: options.store
} else if (options.parent && options.parent.$store) {
// 如果当前参数没有没有 store 对象,但是有 parent 对象,那就说明它依赖于其父组件
// 那么将它的父组件的 store 挂载在 this.$store 上
this.$store = options.parent.$store
}
}
}
store.js 其实就是 vuex 的核心部分,这部分代码我们可以分几部分来看
export class Store {
// 构造函数,默认传入 {}
constructor (options = {}) {
// 如果当前环境是浏览器环境,且没有安装 vuex ,那么就会自动安装
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
// 初始化一些参数
this._committing = false // 是否在进行提交状态标识
this._actions = Object.create(null) // acitons 操作对象
this._actionSubscribers = [] // action 订阅列表
this._mutations = Object.create(null) // mutations操作对象
this._wrappedGetters = Object.create(null) // 封装后的 getters 集合对象
this._modules = new ModuleCollection(options) // vuex 支持 store 分模块传入,存储分析后的 modules
this._modulesNamespaceMap = Object.create(null) // 模块命名空间 map
this._subscribers = [] // 订阅函数集合
this._watcherVM = new Vue() // Vue 组件用于 watch 监视变化
// 替换 this 中的 dispatch, commit 方法,将 this 指向 store
const store = this
const { dispatch, commit } = this
// 其实也可以这么写
// this.dispatch = dispatch.bind(store)
// this.commit = commit.bind(store)
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)
}
// 是否使用严格模式
this.strict = strict
// 数据树
const state = this._modules.root.state
// 加载安装模块
installModule(this, state, [], this._modules.root)
// 重置虚拟 store
resetStoreVM(this, state)
// 如果使用了 plugins 那么挨个载入它们
plugins.forEach(plugin => plugin(this))
// 如果当前环境安装了开发者工具,那么使用开发者工具
if (Vue.config.devtools) {
devtoolPlugin(this)
}
}
// 获取 state, 是从虚拟 state 上获取的,为了区别,所以使用的是 $$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.`)
}
}
commit (_type, _payload, _options) {
// 先统一一下参数,方便后续处理
const {
type,
payload,
options
} = unifyObjectStyle(_type, _payload, _options)
const mutation = { type, payload }
const entry = this._mutations[type]
// 如果在 mutations 列表中不存在当前指定的方法,那么表示传入方法错误,直接返回,开发环境会报错
if (!entry) {
if (process.env.NODE_ENV !== 'production') {
console.error(`[vuex] unknown mutation type: ${type}`)
}
return
}
// 专用修改 state 的方法
this._withCommit(() => {
entry.forEach(function commitIterator (handler) {
handler(payload)
})
})
// 遍历执行订阅者函数,并传入当前设置以执行指定的订阅者
this._subscribers.forEach(sub => sub(mutation, this.state))
// 如果是开发环境,那么当 options 与 options.silent 都存在的情况下,进行报警
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]
// 如果在 action 列表中不存在当前指定的方法,那么表示传入方法错误,直接返回,开发环境会报错
if (!entry) {
if (process.env.NODE_ENV !== 'production') {
console.error(`[vuex] unknown action type: ${type}`)
}
return
}
// 遍历执行订阅者函数,并传入当前设置以执行指定的订阅者
this._actionSubscribers.forEach(sub => sub(action, this.state))
// 如果有多个 entry 那么,使用 Promise.all() 来执行,并返回结果
// 如果只有一个 entry 那么稳了,就执行第一个就可以了
return entry.length > 1
? Promise.all(entry.map(handler => handler(payload)))
: entry[0](payload)
}
// 将函数加入到订阅列表中
subscribe (fn) {
return genericSubscribe(fn, this._subscribers)
}
// action 的订阅
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.`)
}
// 使用 $watch 来监视 getter 的数据状态
return this._watcherVM.$watch(() => getter(this.state, this.getters), cb, options)
}
// 修改 state
replaceState (state) {
// 唯一合法修改 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)
// 把模块安装到 state 上面
installModule(this, this.state, path, this._modules.get(path), options.preserveState)
// 重置虚拟 store
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)
}
// 在 commit 的时候执行,主要是修改 committing 状态,执行回调,修改内容,再将 committing 状态改回去
_withCommit (fn) {
const committing = this._committing
this._committing = true
fn()
this._committing = committing
}
}
// 通用订阅,返回一个函数,这个函数是用于从 subs 中删除插入的 fn 的
function genericSubscribe (fn, subs) {
// 如果 fn 不存在于 subs 列表中,那么添加进 subs 中,否则不操作
if (subs.indexOf(fn) < 0) {
subs.push(fn)
}
return () => {
const i = subs.indexOf(fn)
if (i > -1) {
subs.splice(i, 1)
}
}
}
// 重置 store
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
// 调用 installModule 重新安装模块
installModule(store, state, [], store._modules.root, true)
// 调用 resetStoreVM 重围 VM
resetStoreVM(store, state, hot)
}
/**
* resetStoreVM
* @param {[type]} store store 表示当前 Store 实例
* @param {[type]} state module 表示当前安装的模块
* @param {[type]} hot hot 当动态改变 modules 或者热更新的时候为 true
*/
function resetStoreVM (store, state, hot) {
// 先备份旧有的 vm ,以便以后使用
const oldVm = store._vm
store.getters = {}
const wrappedGetters = store._wrappedGetters
const computed = {}
// 通过Object.defineProperty为每一个getter方法设置get方法
forEachValue(wrappedGetters, (fn, key) => {
computed[key] = () => fn(store)
Object.defineProperty(store.getters, key, {
get: () => store._vm[key],
enumerable: true
})
})
// 先将 Vue.config.silent 存储在临时常量中,这样 new Vue 实例的时候不会报错
const silent = Vue.config.silent
Vue.config.silent = true
// 实例化一个新的 Vue 对象
store._vm = new Vue({
data: {
$$state: state
},
computed
})
// 改成原先的状态
Vue.config.silent = silent
// 如果使用的是严格模式,那么调用 enableStrictMode 来对 store 进行处理
if (store.strict) {
enableStrictMode(store)
}
// 如果 oldVm 存在,那么先判断是否传入了 hot 如果传入了,就先将其 $$state 设为 null . 然后在调用 nextTick 来删除 oldVm 实例
if (oldVm) {
if (hot) {
store._withCommit(() => {
oldVm._data.$$state = null
})
}
Vue.nextTick(() => oldVm.$destroy())
}
}
/**
* installModule
* @param {[type]} store store 表示当前 Store 实例
* @param {[type]} rootState rootState 表示根 state
* @param {[type]} path path 表示当前嵌套模块的路径数组
* @param {[type]} module module 表示当前安装的模块
* @param {[type]} hot hot 当动态改变 modules 或者热更新的时候为 true
*/
function installModule (store, rootState, path, module, hot) {
const isRoot = !path.length
// 获取模块的命名
const namespace = store._modules.getNamespace(path)
// 把当前模块名加入的 store 的 moduleNamespaceMap 中
if (module.namespaced) {
store._modulesNamespaceMap[namespace] = module
}
// 如果当前不是子模块也不是热更新状态,那么就是新增子模块,这个时候要取到父模块
// 然后插入到父模块的子模块列表中
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)
// 使用模块的方法挨个为 mutation, action, getters, child 注册
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)
})
}
/**
* 创建本地上下文环境
*/
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
// 如果是开发环境那且 store._actions[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
// 如果是开发环境那且 store._mutations[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
}
function makeLocalGetters (store, namespace) {
const gettersProxy = {}
const splitPos = namespace.length
Object.keys(store.getters).forEach(type => {
if (type.slice(0, splitPos) !== namespace) return
const localType = type.slice(splitPos)
Object.defineProperty(gettersProxy, localType, {
get: () => store.getters[type],
enumerable: true
})
})
return gettersProxy
}
/**
* registerMutation
* store为当前 Store 实例
* type为 mutation 的 key
* handler 为 mutation 执行的回调函数
* local 为当前模块的路径
*/
function registerMutation (store, type, handler, local) {
// 首先通过 type 拿到对应的 mutations 数组,如果没有则使用空数组
const entry = store._mutations[type] || (store._mutations[type] = [])
// 把 wrappedMutationHandler push 到这个数组中
// 这个函数接收一个参数 payload,这个就是我们在定义 mutation 的时候接收的额外参数
// 这个函数执行的时候会调用 mutation 的回调函数
entry.push(function wrappedMutationHandler (payload) {
handler.call(store, local.state, payload)
})
}
/**
* registerAction
* store为当前 Store 实例
* type为 action 的 key
* handler 为 action 执行的回调函数
* local 为当前模块的路径
*/
function registerAction (store, type, handler, local) {
// 首先通过 type 拿到对应的 actions 数组,如果没有则使用空数组
const entry = store._actions[type] || (store._actions[type] = [])
// 把 wrappedMutationHandler push 到这个数组中
// 这个函数接收一个参数 payload,这个就是我们在定义 action 的时候接收的额外参数
// 这个函数执行的时候会调用 action 的回调函数 cb
entry.push(function wrappedActionHandler (payload, cb) {
// 执行 handler 并将结果存储在 res 中
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)
// 如果 res 是一个 Promise 对象,那么调用 resolve 方法
if (!isPromise(res)) {
res = Promise.resolve(res)
}
// 如果使用了开发者工具的话,那么出错的时候,触发开发者工具的 vuex:error 事件
// 没有使用的话直接返回 res
if (store._devtoolHook) {
return res.catch(err => {
store._devtoolHook.emit('vuex:error', err)
throw err
})
} else {
return res
}
})
}
/**
* registerGetter
* store为当前 Store 实例
* type为 getter 的 key
* rawGetter 为 getter 执行的回调函数
* local 为当前模块的路径
*/
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.getters,
store.state,
store.getters
)
}
}
// 监测 store._vm.state 的变化
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 })
}
/*
* getNestedState 根据 path 查找 state 上的嵌套 state
*/
function getNestedState (state, path) {
return path.length
? path.reduce((state, key) => state[key], state)
: state
}
/**
* 统一对象风格
*/
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 }
}
export function install (_Vue) {
// 如果 Vue.use(vuex) 已经调用过了,那么就不执行操作,且在开发环境下会报错
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 方法来初始化 vuex
applyMixin(Vue)
}
/**
* 从一个数组中找到一个符合条件的元素
* 参数是一个数组和一个回调,回调用于检查元素是否符合要求
* 如果有符合条件的元素,返回第一个元素,否则返回 undefined
* let arr = []
* console.log(arr.filter(el => el > 0)) // undefined
* arr = [4,5,6,7]
* console.log(arr.filter(el => el > 0)) // 4
*/
export function find (list, f) {
return list.filter(f)[0]
}
/**
* 深拷贝方法,默认有一个缓存参数,初始时为空数组,向下递归的时候,会不断的添加内容
*/
export function deepCopy (obj, cache = []) {
// 如果传入的内容为 null 或者 不为 object 那么就表示到最底层了
// 当前拷贝是一个基础的数据类型,此时直接返回内容,停止递归
if (obj === null || typeof obj !== 'object') {
return obj
}
// 如果对象在缓存中找到的话,那就直接返回缓存对象
// 因为虽然是深拷贝,但是原对象中的某几个属性同时引用了某个对象的话
// 这个时候为了与之前对象保持一致,不应该进行深拷贝,而是直接传递引用,比如函数什么的
const hit = find(cache, c => c.original === obj)
if (hit) {
return hit.copy
}
// 判断当前拷贝的是数组还是对象,然后生成对应的类型,然后将当前对象传入到缓存中
const copy = Array.isArray(obj) ? [] : {}
cache.push({
original: obj,
copy
})
// 对对象的每一个属性进行深拷贝处理,这里是用递归
Object.keys(obj).forEach(key => {
copy[key] = deepCopy(obj[key], cache)
})
// 返回内容
return copy
}
/**
* 给对象方法一个 forEach 方法,核心使用的是 ES6 的 Object.keys() 方法
* let obj = {
* a : 5,
* b : 'string',
* c : '/reg/',
* d : function () { console.log('666')}
* }
* let arr = Object.keys(obj)
* console.log(arr) // ['a', 'b', 'c', 'd']
* console.log(Object.prototype.toString.call(arr)) // [object Array]
*/
export function forEachValue (obj, fn) {
Object.keys(obj).forEach(key => fn(obj[key], key))
}
/**
* 判断参数是不是一个对象,返回 true,false
* JavaScript 是通过前三位的二进制码来判断类型的, Object 的前三位是 000 .
* 但是 null 是全 0 。这是一个历史遗留且不好修改的 BUG,故要多添加此层进行判断
* 也可以用 Object.prototype.toString.call(obj) === '[object Object]' 来进行判断
*/
export function isObject (obj) {
return obj !== null && typeof obj === 'object'
}
/**
* 判断一个对象是不是 Promise 对象,只要检查它是不是有 then 方法即可。
* 因为 Promise 的所有返回内容都是一个新的 Promise 对象,所以恒有 then 方法
* 也可以用 Object.prototype.toString.call(val) === '[object Promise]'
*/
export function isPromise (val) {
return val && typeof val.then === 'function'
}
/**
* 断言方法,用于检查传入内容是否正确,
* 如果正常则继续执行, 不正确就抛出异常,这是保证程序正常运行的一种手段
*/
export function assert (condition, msg) {
if (!condition) throw new Error(`[vuex] ${msg}`)
}
import { forEachValue } from '../util'
// Module 类
export default class Module {
// 构造函数
constructor (rawModule, runtime) {
this.runtime = runtime
this._children = Object.create(null)
this._rawModule = rawModule
const rawState = rawModule.state
// 如果 rowSTate 是一个 function 那么就执行它,否则就取它的值。如果它没有值的话,为了程序的稳定,给它一个默认值 {}
this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}
}
// 判断是否是命名空间,如果是则返回 true ,如果不是则返回 false, 这里用到了隐匿类型转换
get namespaced () {
return !!this._rawModule.namespaced
}
// 添加子模块
addChild (key, module) {
this._children[key] = module
}
// 移除子模块
removeChild (key) {
delete this._children[key]
}
// 获取子模块
getChild (key) {
return this._children[key]
}
// 更新模块
update (rawModule) {
// 修改实例的 namespaced
this._rawModule.namespaced = rawModule.namespaced
// 如果更新后的 module 存在 actions, mutations, getters 那么就更新一下内容
if (rawModule.actions) {
this._rawModule.actions = rawModule.actions
}
if (rawModule.mutations) {
this._rawModule.mutations = rawModule.mutations
}
if (rawModule.getters) {
this._rawModule.getters = rawModule.getters
}
}
// 给每个子模块执行指定回调,这里使用了 util 中的 forEachValue 方法来实现
forEachChild (fn) {
forEachValue(this._children, fn)
}
// 对每一个 getter 执行指定回调
forEachGetter (fn) {
// 如果存在就执行指定回调,不存在就不费什么事了,直接返回
if (this._rawModule.getters) {
forEachValue(this._rawModule.getters, fn)
}
}
// 对每一个 action 执行指定回调
forEachAction (fn) {
// 如果存在就执行指定回调,不存在就不费什么事了,直接返回
if (this._rawModule.actions) {
forEachValue(this._rawModule.actions, fn)
}
}
// 对每一个 mutation 执行指定回调
forEachMutation (fn) {
// 如果存在就执行指定回调,不存在就不费什么事了,直接返回
if (this._rawModule.mutations) {
forEachValue(this._rawModule.mutations, fn)
}
}
}
import Module from './module'
import { assert, forEachValue } from '../util'
export default class ModuleCollection {
// 构造函数
constructor (rawRootModule) {
// 注册模块
this.register([], rawRootModule, false)
}
// 拿到模块
get (path) {
return path.reduce((module, key) => {
return module.getChild(key)
}, this.root)
}
// 获取命名空间的名字
getNamespace (path) {
let module = this.root
return path.reduce((namespace, key) => {
module = module.getChild(key)
return namespace + (module.namespaced ? key + '/' : '')
}, '')
}
// 升级模块
update (rawRootModule) {
// 调用 update 方法,这里容易引起疑惑的是模块里也叫这个名字啊,怎么办,这里其实调用的是下面的 update 方法
// 如果要调用本方法进行递归的话,只会使用 this.update()
update([], this.root, rawRootModule)
}
// 注册模块
register (path, rawModule, runtime = true) {
// 如果当前非线上环境,那么使用断言来检查它是不是合法的模块
if (process.env.NODE_ENV !== 'production') {
assertRawModule(path, rawModule)
}
// 实例化一个模块,参数就是要注册的模块
const newModule = new Module(rawModule, runtime)
// 如果是根模块,那么就挂载在 this.root 上面
if (path.length === 0) {
this.root = newModule
} else {
// 如果不是根模块,那么取它的父模块,将其添加到其父模块下
const parent = this.get(path.slice(0, -1))
parent.addChild(path[path.length - 1], newModule)
}
// 如果实例化的对象还有子模块,那么使用 forEachValue 递归注册其所有的子孙模块
if (rawModule.modules) {
forEachValue(rawModule.modules, (rawChildModule, key) => {
this.register(path.concat(key), rawChildModule, runtime)
})
}
}
// 取消模块的注册
unregister (path) {
// 取出模块的父模块,因为使用了模块,所以最上面是 root ,肯定是位于第二级,所以不用担心这里会出问题
const parent = this.get(path.slice(0, -1))
const key = path[path.length - 1]
// 如果当前模块正在执行,那么不能取消注册,否则就取消注册
if (!parent.getChild(key).runtime) return
parent.removeChild(key)
}
}
// 升级模块
function update (path, targetModule, newModule) {
// 如果当前环境不是线上环境,那就代表是开发环境,执行模块断言,检查是否错误
// 之所以这么做,是因为线上环境很多报错与警告会取消
if (process.env.NODE_ENV !== 'production') {
assertRawModule(path, newModule)
}
// 调用指定模块的 update 方法,并将新的模块传入
targetModule.update(newModule)
// 对新模块的子模块进行遍历操作
if (newModule.modules) {
for (const key in newModule.modules) {
if (!targetModule.getChild(key)) {
if (process.env.NODE_ENV !== 'production') {
console.warn(
`[vuex] trying to add a new module '${key}' on hot reloading, ` +
'manual reload is needed'
)
}
return
}
// 递归遍历子模块
update(
path.concat(key),
targetModule.getChild(key),
newModule.modules[key]
)
}
}
}
// 函数断言对象,包含两个属性,一个是用于判断传入内容是否是函数的方法,另一个是说明
const functionAssert = {
assert: value => typeof value === 'function',
expected: 'function'
}
// 对象断言对象,包含两个属性,一个是用于判断传入内容是否是函数或者是一个有属性方法叫 handler 的对象,另一个是说明
const objectAssert = {
assert: value => typeof value === 'function' ||
(typeof value === 'object' && typeof value.handler === 'function'),
expected: 'function or object with "handler" function'
}
const assertTypes = {
getters: functionAssert,
mutations: functionAssert,
actions: objectAssert
}
function assertRawModule (path, rawModule) {
Object.keys(assertTypes).forEach(key => {
// 如果在模块中不存在 getters, mutations, actions 那么就直接返回
// 有的话,就继续进行操作
if (!rawModule[key]) return
// 给对应的断言方法起个别名,类似于 java 中的反射,这样的话,在下面的操作中,就可以使用同一个名字了
const assertOptions = assertTypes[key]
// 挨个进行断言操作,如果不正确,则生成错误信息并显示
forEachValue(rawModule[key], (value, type) => {
assert(
assertOptions.assert(value),
makeAssertionMessage(path, key, type, value, assertOptions.expected)
)
})
})
}
// 生成断言信息,如果错误的情况下,那么就返回它。这里其实就是简单的拼接字符串
function makeAssertionMessage (path, key, type, value, expected) {
let buf = `${key} should be ${expected} but "${key}.${type}"`
if (path.length > 0) {
buf += ` in module "${path.join('.')}"`
}
buf += ` is ${JSON.stringify(value)}.`
return buf
}
// 检查是否是浏览器环境,且浏览器下是否挂载 vue 的 devtools,如果存在就挂载在 devtoolHook 上面,否则给一个 undefined
const devtoolHook =
typeof window !== 'undefined' &&
window.__VUE_DEVTOOLS_GLOBAL_HOOK__
export default function devtoolPlugin (store) {
// 如果不存在 vue devtools ,那么直接返回,不进行操作
if (!devtoolHook) return
store._devtoolHook = devtoolHook
// 触发 devtool 的 vuex:init 事件,并传入 store 。这样是用初始化
devtoolHook.emit('vuex:init', store)
// 监听 vuex:travel-to-state 方法,用于更换 store 状态
devtoolHook.on('vuex:travel-to-state', targetState => {
store.replaceState(targetState)
})
// 订阅 store 的 mutation 事件,如果触发了 mutation 事件,那么就执行回调
// 回调是触发 devtool 的 vuex:mutations 方法
store.subscribe((mutation, state) => {
devtoolHook.emit('vuex:mutation', mutation, state)
})
}
import { deepCopy } from '../util'
// 先给参数赋予默认值,这样方便在参数缺省的情况下执行不会出错
export default function createLogger ({
collapsed = true,
filter = (mutation, stateBefore, stateAfter) => true,
transformer = state => state,
mutationTransformer = mut => mut,
logger = console
} = {}) {
// 从这里我们可以看出,这里利用了函数式编程,返回了一个函数,当我们执行 logger 的时候,其实就是调用这个函数
return store => {
// 深拷贝了 store.state 这么做的原因是用于和后来状态进行对比,毕竟 logger 系统就是用于干这个的
let prevState = deepCopy(store.state)
// 给 sotre 的 mutation 事件添加订阅,如果触发,执行下面传入的函数
store.subscribe((mutation, state) => {
// 先判断 console 是否存在,如果不存在,那就 GG 了,没法下去了,直接返回
if (typeof logger === 'undefined') {
return
}
// 深拷贝一下 state ,也就是存储一下当前的状态
const nextState = deepCopy(state)
// 根据传入 filter() 判断,默认为 true 直接执行
if (filter(mutation, prevState, nextState)) {
// 初始化一些参数,如时间,格式文本函数,信息等
const time = new Date()
const formattedTime = ` @ ${pad(time.getHours(), 2)}:${pad(time.getMinutes(), 2)}:${pad(time.getSeconds(), 2)}.${pad(time.getMilliseconds(), 3)}`
const formattedMutation = mutationTransformer(mutation)
const message = `mutation ${mutation.type}${formattedTime}`
const startMessage = collapsed
? logger.groupCollapsed
: logger.group
// 调用 console.log(groupCollapsed) 输出内容,如果出问题,不用管,直接处理输出 message
try {
startMessage.call(logger, message)
} catch (e) {
console.log(message)
}
// 具体的打印内容
logger.log('%c prev state', 'color: #9E9E9E; font-weight: bold', transformer(prevState))
logger.log('%c mutation', 'color: #03A9F4; font-weight: bold', formattedMutation)
logger.log('%c next state', 'color: #4CAF50; font-weight: bold', transformer(nextState))
// 执行 console.groupEnd() 其实这里面是和上面的那个异常处理是对应的,同理,出了问题执行 console.log()
try {
logger.groupEnd()
} catch (e) {
logger.log('—— log end ——')
}
}
// 把当初状态存储在常驻变量中,方便下次与新的状态比较
prevState = nextState
})
}
}
/**
* 字符串重复方法,返回指定字符串多次重复后的结果
* 在 ES6 中已经可以写成 return str.repeat(times)
*/
function repeat (str, times) {
return (new Array(times + 1)).join(str)
}
/**
* 在数字前面补 0 到指定位数,并返回结果
* 在 ES6 中已经可以写成 return num.toString().padStart(maxLength, '0')
* 在 ES5 中也可以写成 return (repeat('0', maxLength) + num).slice(-maxLength)
*/
function pad (num, maxLength) {
return repeat('0', maxLength - num.toString().length) + num
}