vuex源码分析3.0.1(原创)
2018-08-02 06:21:50来源:博客园 阅读 ()
前言
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一个报错信息。
写作不易,欢迎打赏!微信哦。
标签:
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有
- Javascript判断图片尺寸大小实例分析 2019-12-06
- EChart绘制风速风向曲线分析图 2019-08-14
- Vue核心知识——computed和watch的细节全面分析 2019-08-14
- Vue.use()源码分析且执行后干什么了 2019-08-14
- 弹窗组件的开发 2019-05-22
IDC资讯: 主机资讯 注册资讯 托管资讯 vps资讯 网站建设
网站运营: 建站经验 策划盈利 搜索优化 网站推广 免费资源
网络编程: Asp.Net编程 Asp编程 Php编程 Xml编程 Access Mssql Mysql 其它
服务器技术: Web服务器 Ftp服务器 Mail服务器 Dns服务器 安全防护
软件技巧: 其它软件 Word Excel Powerpoint Ghost Vista QQ空间 QQ FlashGet 迅雷
网页制作: FrontPages Dreamweaver Javascript css photoshop fireworks Flash