vuex源码分析3.0.1(原创)
Posted chenmeng2062
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vuex源码分析3.0.1(原创)相关的知识,希望对你有一定的参考价值。
前言
chapter1 store构造函数
1.constructor
2.get state和set state
3.commit
4.dispatch
5.subscribe和subscribeAction
6.watch和replaceState
7.registerModule和unregisterModule
8.hotUpdate和_withCommit
chapter2 export install
Q:Vuex如何实现装载的?
chapter3 辅助函数
1.registerMutation、registerAction、registerGetter
2.enableStrictMode、getNestedState
3.unifyObjectStyle(type, payload, options)
1.store构造函数 /part1
1.constructor
源码分析
constructor (options = {}) {
//安装Vue对象
if (!Vue && typeof window !== ‘undefined‘ && window.Vue) {
console.log("window.vue");
install(window.Vue)
}
//开发环境对Vue、Promise和Store的判断
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.`)
}
//options包括插件选项、严格模式选项
const {
plugins = [],
strict = false
} = options
// 存储内部的状态
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()
// 绑定commit和dispatch
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)
}
// 严格模式
this.strict = strict
const state = this._modules.root.state
// 初始化根模块,或者安装子模块
installModule(this, state, [], this._modules.root)
//初始化vm
resetStoreVM(this, state)
// 应用插件
plugins.forEach(plugin => plugin(this))
if (Vue.config.devtools) {
devtoolPlugin(this)
}
}
2.get state和set state
ES6的get和set是取值和存值的函数,这是是对属性state拦截存取行为。
示例1
E:vuex>node
//类的声明,属性prop进行存取拦截
> class MyClass {
... constructor() {
..... // ...
..... }
... get prop() {
..... return ‘getter‘;
..... }
... set prop(value) {
..... console.log(‘setter: ‘ + value);
..... }
... }
undefined
> let inst = new MyClass();
undefined
//设置prop时,根据程序逻辑会console.log
> inst.prop = 123;
setter: 123
123
//获取prop,根据return返回"getter"字符串
> inst.prop
‘getter‘
源码1
//取值返回的是this属性
get state () {
return this._vm._data.$$state
}
//如果在非生产环境,那么修改state就会使用assert打印错误信息
set state (v) {
if (process.env.NODE_ENV !== ‘production‘) {
assert(false, `use store.replaceState() to explicit replace store state.`)
}
}
3.commit
commit (_type, _payload, _options) {
// check object-style commit检查对象风格提交
const {
type,
payload,
options
} = unifyObjectStyle(_type, _payload, _options)
//mutation的type判断,也就是entry,如果不存在,那么打印错误信息“不存在的mutation type”
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;
}
//处理entry并订阅它
this._withCommit(() => {
entry.forEach(function commitIterator (handler) {
handler(payload)
})
})
this._subscribers.forEach(sub => sub(mutation, this.state))
//开发模式下的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‘
)
}
}
(1)const { type, payload,options}=unify..........这是ES6的解构赋值。(node环境执行的哦)
示例2
E:vuex>node
> const person = {
... name: ‘little bear‘,
... age: 18,
... sex: ‘男‘
... }
undefined
> let { name,age,sex } = person
undefined
> name
‘little bear‘
(2)this._withCommit(...)小括号内的部分总体上说是_withCommit的fn参数。
this._withCommit()中有对this._committing进行设置,首先this._committing = false赋值给中间变量,接下来提交前设为true,fn调用结束后再通过中间变量设为初始值。
接下来说说entry。entry就是mutations的type也就是某个函数。可是明明forEach方法是数组啊。其实通过this._mutations[type]获取到就是一个数组。那么对数组的元素handler进行调用。entry
类似如下内容:
(3)this._subscribers.forEach(sub => sub(mutation, this.state))是_subscribers遍历收集来的actions并执行。我们要注意到actions的使用也有commit提交,不过是异步的。所以这里的actions执行是为了补充刚刚同步提交的方式。
图示1
(4)process.env.NODE_ENV !== ‘production‘ &&options && options.silent
检查选项,silent是静默选项,如果使用了silent,那么告知"silent已经被移除,请在dev-tool中使用过滤器功能。
4,dispatch
dispatch (_type, _payload) {
// 检查数组风格的分发
const {
type,
payload
} = unifyObjectStyle(_type, _payload)
const action = { type, payload }
//从this._actions拿到type对应的事件类型
const entry = this._actions[type]
//如果entry也就是事件类型不存在,那么打印信息"vuex不知道的action类型"
if (!entry) {
if (process.env.NODE_ENV !== ‘production‘) {
console.error(`[vuex] unknown action type: ${type}`)
}
return
}
//_actionSubscribers遍历每个订阅
this._actionSubscribers.forEach(sub => sub(action, this.state))
//如果entry.length大于1,那么返回promise
return entry.length > 1
? Promise.all(entry.map(handler => handler(payload)))
: entry[0](payload)
}
5.subscribe和subscribeAction
subscribe订阅store的mutation。回调函数会在每个mutaion完成时触发。
示例
const myPlugin = store => {
// 当 store 初始化后订阅
store.subscribe((mutation, state) => {
//回调函数在每次mutation完成之后调用
state.count++;
})
}
const store = new Vuex.Store({
state:{
count:5
},
mutations:{
increment(state,payload){
state.count=state.count*payload;
}
},
plugins: [myPlugin]
})
//提交"increment"事件
store.commit("increment",20)
//最终store.state.count等于5*20+1=101。
subscribeAction订阅action。回调函数会在每个action完成时触发。
const myPlugin2 = store => {
// 当 store 初始化后订阅
store.subscribeAction((action, state) => {
//每次action完成后回调函数都会被触发
state.huge--;
})
}
const store = new Vuex.Store({
state:{
huge:2000
},
mutations:{
REDUCE(state,payload){
state.huge=state.huge-payload
}
},
actions:{
reduce({commit,state},payload){
commit("REDUCE",payload)
}
},
plugins: [myPlugin2]
})
store.dispatch("reduce",500)
//store.state.huge结果2000-500-1等于1499
源码分析
subscribe (fn) {
//fn即刚才说的每次mutation之后的回调函数
return genericSubscribe(fn, this._subscribers)
}
subscribeAction (fn) {
return genericSubscribe(fn, this._actionSubscribers)
}
//subscribe和subscribeAction返回的是一个箭头函数
function genericSubscribe (fn, subs) {
//订阅fn,那么会push到this._subscribers或者this._actionSubscribers数组
if (subs.indexOf(fn) < 0) {
subs.push(fn)
}
return () => {
//箭头函数在需要回调的时候再从数组里裁剪出fn元素
const i = subs.indexOf(fn)
if (i > -1) {
subs.splice(i, 1)
}
}
}
可以看出,genericSubscribe功能是对订阅数组的处理,先存进数组,需要的时候再取出来。
6.watch和replaceState
源码分析
watch (getter, cb, options) {
//如果传入的getter不是function,那么打印信息"store.watch只接受一个函数"
if (process.env.NODE_ENV !== ‘production‘) {
assert(typeof getter === ‘function‘, `store.watch only accepts a function.`)
}
//返回Vue.$watch方法,响应式监听() => getter(this.state, this.getters)返回的值
//如果发生变化,那么cb回调函数触发
//options包括选项:deep,选项:immediate
return this._watcherVM.$watch(() => getter(this.state, this.getters), cb, options)
}
示例
<!--Vue API中$watch的用法-->
<div id="app">
<button @click="addOne">加一</button>
</div>
<script>
let vm= new Vue({
el:"#app",
data:{
a:0
},
created:function(){
//$watch监听第一个函数返回的只,一旦发生变化,那么执行回调函数
this.$watch(function(){
return this.a;
},function(newValue,oldValue){
console.log(newValue)
})
},
methods:{
addOne(){
this.a=1;
}
}
})
</script>
示例
//replaceState整体替换state,变化引起回调发生
const store=new Vuex.Store({
state:{
count:0
}
})
store.watch(function(){
return store.state;
},function(){
console.log(store.state.count)//20
})
store.replaceState({count:20})
示例
//通过mutation改变state,触发watch回调
const store2=new Vuex.Store({
state:{
count:100
},
mutations:{
ADDONE(state){
state.count++;
}
}
})
store2.watch(function(){
return store2.state.count
},function(){
console.log(store2.state.count)
//101
}
)
store2.commit("ADDONE");
源码分析
replaceState (state) {
this._withCommit(() => {
this._vm._data.$$state = state
})
}
通过传入一个新state对象,替换旧state。
示例
const store=new Vuex.Store({
state:{
count:1,
num:20
}
})
store.replaceState({count:0});
//通过替换,旧的state不存在,只有更新后的state
store.state.count//等于0
store.state.num//undefined
7.registerModule和unregisterModule
示例
源码分析
registerModule (path, rawModule, options = {}) {
//传入的第一个参数要么是数组,要么是字符串,字符串会转化为字符串为元素的数组
if (typeof path === ‘string‘) path = [path]
//开发环境下的调试信息
if (process.env.NODE_ENV !== ‘production‘) {
//如果path不能转为数组或者不是数组,那么打印"模块path必须是字符串或者数组"
assert(Array.isArray(path), `module path must be a string or an Array.`)
//如果传入的path为[]空数组,那么打印"不能使用registerModule来注册根模块"
assert(path.length > 0, ‘cannot register the root module by using registerModule.‘)
}
//在store._modules上注册模块
this._modules.register(path, rawModule)
//安装模块
installModule(this, this.state, path, this._modules.get(path), options.preserveState)
// reset store to update getters...
//以当前state更新store.getters
resetStoreVM(this, this.state)
}
源码分析
function installModule (store, rootState, path, module, hot) {
const isRoot = !path.length
const namespace = store._modules.getNamespace(path)
// 注册命名空间的映射数组_modulesNamespaceMap
if (module.namespaced) {
store._modulesNamespaceMap[namespace] = module
}
//hot,即options 可以包含 preserveState: true 以允许保留之前的 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)
//遍历mutation并注册mutation,会因为namespaced而不同
module.forEachMutation((mutation, key) => {
const namespacedType = namespace + key
registerMutation(store, namespacedType, mutation, local)
})
//遍历action并注册action
module.forEachAction((action, key) => {
//如果action.root为true,那么type等于key索引值,
//即全局action,无论是子模块还是子模块的子模块都如此
//如果action.root为false,那么type直接取namespacType
const type = action.root ? key : namespace + key
const handler = action.handler || action
registerAction(store, type, handler, local)
})
//遍历getter并注册getterts,会因为namespaced而不同
module.forEachGetter((getter, key) => {
const namespacedType = namespace + key
registerGetter(store, namespacedType, getter, local)
})
//遍历子模块,并递归调用installModule
module.forEachChild((child, key) => {
installModule(store, rootState, path.concat(key), child, hot)
})
}
源码分析
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.`)
}
//取消注册,那么store._modules.root._children就不会定义myModule属性了
this._modules.unregister(path)
this._withCommit(() => {
//getNestedState获取到父级state
const parentState = getNestedState(this.state, path.slice(0, -1))
//Vue删除相应的module内容
Vue.delete(parentState, path[path.length - 1])
})
//以当前的this重置store
resetStore(this)
}
8.hotUpdate和_withCommit
源码分析
//热重载
hotUpdate (newOptions) {
this._modules.update(newOptions)
resetStore(this, true)
}
Vuex 支持在开发过程中热重载 mutation、module、action 和 getter。
_withCommit (fn) {
//每次提交的时候,内部代码都会传进来一个箭头函数
const committing = this._committing
this._committing = true
fn()
this._committing = committing
}
2.export install
示例
<script src="js/vue.js"></script>
<!--这行语句安装了window.Vue-->
<script>
let Vue;
if (!Vue && typeof window !== ‘undefined‘ && window.Vue) {
console.log("window.vue");
install(window.Vue)
}
function install (_Vue) {
Vue = _Vue
console.log(Vue);
//applyMixin(Vue)是为了在Vue初始化之前(beforeCreate)来完成vuex的初始化
//因为2版本才提供了beforeCreate这个钩子函数
//applyMixin主要逻辑:if (version >= 2) {Vue.mixin({ beforeCreate: vuexInit })} else {}
}
</script>
从中可以看出vuex的初始化过程,以Vue2版本为为例:
源码分析
export function install (_Vue) {
//那么问题来了,为什么要使用let Vue这个文件一个全局变量呢?主要是为了避免重复安装
if (Vue && _Vue === Vue) {
if (process.env.NODE_ENV !== ‘production‘) {
//如果已经安装过,那么Vue就等于window.Vue为什么呢?
//Vue.use(plugin)方法会调用export的install方法,那么调用中使用Vue=_Vue赋值语句
console.error(
‘[vuex] already installed. Vue.use(Vuex) should be called only once.‘
)
}
return
}
Vue = _Vue
applyMixin(Vue)
}
install调用逻辑分析:
3.辅助函数
1.registerMutation、registerAction、registerGetter
function registerMutation (store, type, handler, local) {
//将type属性添加到_mutations对象,其初始值为空数组[]
const entry = store._mutations[type] || (store._mutations[type] = [])
//我们应该记得mutation是一个函数,那么function.call做一个继承,local.state和payload都应用于store对象
entry.push(function wrappedMutationHandler (payload) {
handler.call(store, local.state, payload)
})
}
........
registerMutation(store, namespacedType, mutation, local)
function registerAction (stobre, type, handler, local) {
//_actions具有type属性,其初始值为一个数组
const entry = store._actions[type] || (store._actions[type] = [])
entry.push(function wrappedActionHandler (payload, cb) {
//继承于store对象
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,那么相当于直接返回含有res内容的promise对象
if (!isPromise(res)) {
res = Promise.resolve(res)
}
//_devtoolHook判断
if (store._devtoolHook) {
//拦截promise错误
return res.catch(err => {
store._devtoolHook.emit(‘vuex:error‘, err)
throw err
})
} else {
//返回res
return res
}
})
}
.........
registerAction(store, type, handler, local)
我们应该还记得action是可以写异步操作的。
function registerGetter (store, type, rawGetter, local) {
//如果对应已getter存在,进入分支,打印说"vuex重复的getter键"
if (store._wrappedGetters[type]) {
if (process.env.NODE_ENV !== ‘production‘) {
console.error(`[vuex] duplicate getter key: ${type}`)
}
return
}
store._wrappedGetters[type] = function wrappedGetter (store) {
//通过当前local和store返回rawGetter对象
return rawGetter(
local.state, // local state
local.getters, // local getters
store.state, // root state
store.getters // root getters
)
}
}
2.enableStrictMode、getNestedState
if (store.strict) {
enableStrictMode(store)
}
//enableStrictMode功能是允许new vm的严格模式
function enableStrictMode (store) {
//侦听this._data.$$state也就是state
store._vm.$watch(function () { return this._data.$$state }, () => {
//state变化,回调函数触发
//store._committing为False,那么打印"不要在mutation处理器外部提交state
if (process.env.NODE_ENV !== ‘production‘) {
assert(store._committing, `do not mutate vuex store state outside mutation handlers.`)
}
//deep:true,跟踪对象内部属性的变化,sync:true,同步
}, { deep: true, sync: true })
}
首先,getNestedState的功能是父级state对象。
function getNestedState (state, path) {
return path.length
//state为初始值,接下来遍历path数组,并以state[key]取得state对象
? path.reduce((state, key) => state[key], state)
: state
}
那么为什么这个key比如state["myModule"]的索引就能拿到对应的state呢?这是因为state对象长这个样子。
示例
let vm= new Vue({
el:"#app",
})
const store=new Vuex.Store({
state:{
count:0
}
})
function getNestedState (state, path) {
return path.length
? path.reduce((state, key) => state[key], state)
: state
}
let myModule={
state:{
count:8
}
}
store.registerModule("myModule",myModule)
//找到父级state对象
//["myModule"].slice(0,-1)等于[]
let parentState=getNestedState(store.state,["myModule"].slice(0,-1))
console.log(parentState)
结果如下:
3.unifyObjectStyle(type, payload, options)
首先运行一下这个函数,它可以传入3个参数(payload)。由于process是nodejs环境的变量,那么在nodejs环境中运行。
它的功能是把提交数据对象风格化。
//nodejs环境输入function代码
E:vuex>node
> function isObject (obj) {
... return obj !== null && typeof obj === ‘object‘
... }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 }
... }
undefined
//nodejs环境中调用刚刚定义的unifyObjectStyle。
> unifyObjectStyle("login",{name:"vicky",password:"123"})
{ type: ‘login‘,
payload: { name: ‘vicky‘, password: ‘123‘ },
options: undefined }
> unifyObjectStyle({type:"login",payload:{name:"vicky",password:"123"}})
{ type: ‘login‘,
payload: { type: ‘login‘, payload: { name: ‘vicky‘, password: ‘123‘ } },
options: undefined }
它讨论了两种情况。(1)如果type.type不存在,那么就是以参数风格的提交,按照最终的对象格式return。(2)如果type.type存在,也就是对象风格的提交,那么就让对象的type和payload重新赋值。然后return。以最终实现对象风格的统一。
而process的部分是对type的值进行判断,如果不是string,那么assert一个报错信息。
写作不易,欢迎打赏!微信哦。
以上是关于vuex源码分析3.0.1(原创)的主要内容,如果未能解决你的问题,请参考以下文章
Android 插件化VirtualApp 源码分析 ( 目前的 API 现状 | 安装应用源码分析 | 安装按钮执行的操作 | 返回到 HomeActivity 执行的操作 )(代码片段