# vuex 源码
# Vue.use(Vuex)
- from
vue2
souce codes
function initUse (Vue) {
Vue.use = function (plugin) {
var installedPlugins = (this._installedPlugins || (this._installedPlugins = []));
if (installedPlugins.indexOf(plugin) > -1) {
return this
}
var args = toArray(arguments, 1);
// this as vue , treat it as the first item
args.unshift(this);
if (typeof plugin.install === 'function') {
plugin.install.apply(plugin, args);
} else if (typeof plugin === 'function') {
plugin.apply(null, args);
}
installedPlugins.push(plugin);
return this
};
}
# f:store.js
vuex\src\store.js
class Store
export class Store {
constructor (options = {}) { // yiki: new Vuex(option)
if (!Vue && typeof window !== 'undefined' && window.Vue) {
// #0
install(window.Vue)
}
// assert(condition, msg) if(!condition) throw error
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 // get from opiton
// store internal state
this._committing = false// 是否在进行提交状态标识
this._actions = Object.create(null)
this._actionSubscribers = []
this._mutations = Object.create(null)
this._wrappedGetters = Object.create(null)
// #2
this._modules = new ModuleCollection(options)// see module-collection.js
this._modulesNamespaceMap = Object.create(null)// 用于存储模块命名空间的关系
this._subscribers = []// 订阅函数集合
this._watcherVM = new Vue() // vue组件用于watch监视变化
this._makeLocalGettersCache = Object.create(null)// 用来存放生成的本地 getters 的缓存
// bind commit and dispatch to self
const store = this
const { dispatch, commit } = this// 替换原型中的dispatch 和 commit
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
/*
初始化 根模块。
并且也递归的注册所有子模块。
并且收集所有模块的 getters 放在 this._wrappedGetters 里面*/
// #5 #4
installModule(this, state, [], this._modules.root)
/*
初始化 store._vm 响应式的
并且注册 _wrappedGetters 作为 computed 的属性*/
// #9
resetStoreVM(this, state)
/*
把实例对象 store 传给插件函数,执行所有插件*/
plugins.forEach(plugin => plugin(this))
/*
初始化 vue-devtool 开发工具。
参数 devtools 传递了取 devtools 否则取Vue.config.devtools 配置*/
const useDevtools = options.devtools !== undefined ? options.devtools : Vue.config.devtools
if (useDevtools) {
devtoolPlugin(this)
}
}
install() #0
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
// #1
applyMixin(Vue)
}
installModule #5
function installModule (store, rootState, path, module, hot) {
const isRoot = !path.length
const namespace = store._modules.getNamespace(path)
// register in namespace map
if (module.namespaced) {
...
}
// set state
if (!isRoot && !hot) {
const parentState = getNestedState(rootState, path.slice(0, -1))
const moduleName = path[path.length - 1]
store._withCommit(() => {
if (__DEV__) {
...
}
Vue.set(parentState, moduleName, module.state)
})
}
/*
module.context 这个赋值主要是给 helpers 中 mapState、mapGetters、mapMutations、mapActions四个辅助函数使用的。
生成本地的dispatch、commit、getters和state。
主要作用就是抹平差异化,不需要用户再传模块参数 */
const local = module.context = makeLocalContext(store, namespace, path)
// 遍历注册 mutation
module.forEachMutation((mutation, key) => {
const namespacedType = namespace + key
// #6
registerMutation(store, namespacedType, mutation, local)
})
// 遍历注册 action
module.forEachAction((action, key) => {
const type = action.root ? key : namespace + key
const handler = action.handler || action
// #7
registerAction(store, type, handler, local)
})
// 遍历注册 getter
module.forEachGetter((getter, key) => {
const namespacedType = namespace + key
// #8
registerGetter(store, namespacedType, getter, local)
})
// 遍历注册 子模块
module.forEachChild((child, key) => {
installModule(store, rootState, path.concat(key), child, hot)
})
}
registerMutation(...) #6
/**
* 注册 mutation
* @param {Object} store 对象
* @param {String} type 类型
* @param {Function} handler 用户自定义的函数
* @param {Object} local local 对象
*/
function registerMutation (store, type, handler, local) {
// 收集的所有的mutations找对应的mutation函数,没有就赋值空数组
const entry = store._mutations[type] || (store._mutations[type] = [])
// 最后 mutation
entry.push(function wrappedMutationHandler (payload) {
/**
* mutations: {
* pushProductToCart (state, { id }) {
* console.log(state);
* }
* }
* 也就是为什么用户定义的 mutation 第一个参数是state的原因,第二个参数是payload参数
*/
handler.call(store, local.state, payload)
})
}
registerAction(...) #7
/**
* 注册 mutation
* @param {Object} store 对象
* @param {String} type 类型
* @param {Function} handler 用户自定义的函数
* @param {Object} local local 对象
*/
function registerAction (store, type, handler, local) {
const entry = store._actions[type] || (store._actions[type] = [])
// payload 是actions函数的第二个参数
entry.push(function wrappedActionHandler (payload) {
/**
* 也就是为什么用户定义的actions中的函数第一个参数有
* { dispatch, commit, getters, state, rootGetters, rootState } 的原因
* actions: {
* checkout ({ commit, state }, products) {
* console.log(commit, state);
* }
* }
*/
let res = handler.call(store, {
dispatch: local.dispatch,
commit: local.commit,
getters: local.getters,
state: local.state,
rootGetters: store.getters,
rootState: store.state
}, payload)
/**
* export function isPromise (val) {
return val && typeof val.then === 'function'
}
* 判断如果不是Promise Promise 化,也就是为啥 actions 中处理异步函数
也就是为什么构造函数中断言不支持promise报错的原因
vuex需要Promise polyfill
assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`)
*/
if (!isPromise(res)) {
res = Promise.resolve(res)
}
// devtool 工具触发 vuex:error
if (store._devtoolHook) {
// catch 捕获错误
return res.catch(err => {
store._devtoolHook.emit('vuex:error', err)
// 抛出错误
throw err
})
} else {
// 然后函数执行结果
return res
}
})
}
registerGetter(...) #8
/**
* 注册 getter
* @param {Object} store Store实例
* @param {String} type 类型
* @param {Object} rawGetter 原始未加工的 getter 也就是用户定义的 getter 函数
* @examples 比如 cartProducts: (state, getters, rootState, rootGetters) => {}
* @param {Object} local 本地 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) {
/**
* 这也就是为啥 getters 中能获取到 (state, getters, rootState, rootGetters) 这些值的原因
* getters = {
* cartProducts: (state, getters, rootState, rootGetters) => {
* console.log(state, getters, rootState, rootGetters);
* }
* }
*/
return rawGetter(
local.state, // local state
local.getters, // local getters
store.state, // root state
store.getters // root getters
)
}
}
resetStoreVM(...) #9
function resetStoreVM (store, state, hot) {
// 存储一份老的Vue实例对象 _vm
const oldVm = store._vm
// bind store public getters
// 绑定 store.getter
store.getters = {}
// reset local getters cache
// 重置 本地getters的缓存
store._makeLocalGettersCache = Object.create(null)
// 注册时收集的处理后的用户自定义的 wrappedGetters
const wrappedGetters = store._wrappedGetters
// 声明 计算属性 computed 对象
const computed = {}
// 遍历 wrappedGetters 赋值到 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.
/**
* partial 函数
* 执行函数 返回一个新函数
export function partial (fn, arg) {
return function () {
return fn(arg)
}
}
*/
computed[key] = partial(fn, store)
// getter 赋值 keys
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
// 使用一个 Vue 实例对象存储 state 树
// 阻止警告 用户添加的一些全局mixins
// 声明变量 silent 存储用户设置的静默模式配置
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
// 开启严格模式 执行这句
// 用 $watch 观测 state,只能使用 mutation 修改 也就是 _withCommit 函数
if (store.strict) {
enableStrictMode(store)
}
// 如果存在老的 _vm 实例
if (oldVm) {
// 热加载为 true
if (hot) {
// dispatch changes in all subscribed watchers
// to force getter re-evaluation for hot reloading.
// 设置 oldVm._data.$state = null
store._withCommit(() => {
oldVm._data.$state = null
})
}
// 实例销毁
Vue.nextTick(() => oldVm.$destroy())
}
}
# f:module-collection.js
class ModuleCollection #2
module\module-collection.js
- 收集模块,构造模块树结构
rawRootModule
fromVuex.Store.option
export default class ModuleCollection {
constructor (rawRootModule) {
// register root module (Vuex.Store options)
// #3
this.register([], rawRootModule, false)
}
...
register(...) #3
/**
yiki:循环调用模块注册
* @param {Array} path 路径
* @param {Object} rawModule 原始未加工的模块
* @param {Boolean} runtime 默认是 true
*/
register (path, rawModule, runtime = true) {
...
const newModule = new Module(rawModule, runtime)
if (path.length === 0) {
this.root = newModule
} else {
const parent = this.get(path.slice(0, -1))
parent.addChild(path[path.length - 1], newModule)
}
// register nested modules
if (rawModule.modules) {
forEachValue(rawModule.modules, (rawChildModule, key) => {
this.register(path.concat(key), rawChildModule, runtime)
})
}
}
# f:module.js
class Module this._modules #4
vuex\src\module\module.js
export default class Module {
constructor (rawModule, runtime) {
this.runtime = runtime
// Store some children item
this._children = Object.create(null)
// Store the origin module object which passed by programmer
this._rawModule = rawModule
const rawState = rawModule.state
// Store the origin module's state
this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}
}
...
# f:mixin.js
applyMixin(Vue) #1
export default function (Vue) {
const version = Number(Vue.version.split('.')[0])
if (version >= 2) {
Vue.mixin({ beforeCreate: vuexInit })
} else {
// for 1.x backwards compatibility
...
}
/**
* Vuex init hook, injected into each instances init hooks list.
*/
function vuexInit () {
const options = this.$options
// store injection
if (options.store) {
this.$store = typeof options.store === 'function' // inject into instanc of vue
? options.store()
: options.store
} else if (options.parent && options.parent.$store) {
this.$store = options.parent.$store
}
}
}