Vuex源码分析,实现简易版的Vuex
admin
2024-01-29 06:52:13
0

vuex是什么?

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
https://vuex.vuejs.org/zh/

vuex 是 Vue 配套的公共数据管理库,它可以把一些共享的数据,保存到 vuex 中,方便整个程序中的任何组件直接获取或修改我们的公共数据。使用Vuex的时候需要用到Vue的use方法,因此它的本质是一个插件。

Vuex结构

store/index.js(Vuex入口文件)

import Vue from 'vue'
import Vuex from 'vuex'Vue.use(Vuex)export default new Vuex.Store({// 用于保存全局共享数据state: {name: 'lnj'},getters: { }// 用于同步修改共享数据mutations: { },// 用于异步修改共享数据actions: { },// 用户模块化共享数据modules: { }
})

main.js(Vue中引入Vuex)

import Vue from 'vue' 
import App from './App.vue' 
import store from './store' Vue.config.productionTip = falsenew Vue({ 
store, 
render: h => h(App) 
}).$mount('#app')

通过分析它的基本结构,实现一个简易版的Vuex。

添加全局$store

把官方的vuex替换成自己的myVuex

import Vue from 'vue'
import Vuex from './myVuex'Vue.use(Vuex)
...

在使用Vuex的时候会通过Vuex.Store创建一个仓库,而且为了保证每个Vue实例中都能通过this.$store拿到仓库,还需要给每个Vue实例添加一个$store属性。

官方初始化Store的部分代码,如下所示:

function applyMixin (Vue) {var version = Number(Vue.version.split('.')[0]);if (version >= 2) {Vue.mixin({ beforeCreate: vuexInit });} else {var _init = Vue.prototype._init;Vue.prototype._init = function (options) {if ( options === void 0 ) options = {};options.init = options.init? [vuexInit].concat(options.init): vuexInit;_init.call(this, options);};}function vuexInit () {var options = this.$options;if (options.store) {this.$store = typeof options.store === 'function'? options.store(): options.store;} else if (options.parent && options.parent.$store) {this.$store = options.parent.$store;}}
}

mvVuex.js中添加全局$store,实现自己的Vuex

// install方法会在外界调用Vue.use的时候执行,
//并且在执行的时候会把Vue实例和一些额外的参数传递出来
const install = (vm, options)=>{vm.mixin({// 在初始化$options之前,给每一个Vue实例添加一个$storebeforeCreate(){// Vue创建实例时会先创建根组件,再创建子组件,if(this.$options && this.$options.store){// 如果根组件,默认就有store,直接赋值即可this.$store = this.$options.store;}else{// 不是根组件,默认没有store,需要把父组件的$store赋值给它this.$store = this.$parent.$store;}}});
}
// 创建一个Store类,即初始结构
class Store {constructor(options){this.state = options;}
}
export default {install,Store
}

实现响应式state

Vuex的state区别与全局变量的一点,就是state是响应式的,那么如何实现响应式的state呢?

// 官方示例中,是直接重写,用Object.defineProperties实现
Object.defineProperties( Store.prototype, prototypeAccessors$1 );

重写比较麻烦的话,可以直接利用new Vue()的时候,传入的data是响应式来实现

class Store {constructor(options){this.vm = new Vue({data:{state:options.state}})}// 自动触发get,转换this.vm,使得获取state时通过`this.$store.state`// 而不是`this.$store.vm.state`获取stateget state(){ return this.vm.state }
}

当然,也可以使用vue中自带的方法来实现响应式

class Store {constructor(options){Vue.util.defineReactive(this,'state',options.state)}
}

实现Getters

wrappedGetters(options){
let getters = options.getters || {};
this.getters = {};
for(let key in getters){Object.defineProperty(this.getters, key, {get:()=>{return getters[key](this.state);}})
}
}

store/index.js文件定义getters

// store/index.js
state: {name: 'HelloWorld'
},
getters: {myName(state){return '姓名:'+ state.name;}
},

App文件中使用

// App.vue

实现Mutation

wrappedMutations(options){
let mutations = options.mutations || {};
this.mutations = {};
for(let key in mutations){this.mutations[key] = (payload)=>{ // 10mutations[key](this.state, payload); // addNum(this.state, 10);}}
}
// 再实现一个commit方法用来触发Mutation,需要用箭头函数,这里的this指向state
commit=(type, payload)=>{this.mutations[type](payload);
}

实现Actions

wrappedActions(options){
let actions = options.actions || {};
this.actions = {};
for(let key in actions){this.actions[key] = (payload)=>{actions[key](this, payload);}
}
}
// 再实现一个dispatch方法用来触发Actions,需要用箭头函数,这里的this指向state
dispatch=(type, payload)=>{this.actions[type](payload);
}

完整示例一

App.vue


store/index.js

import Vue from 'vue'
import Vuex from './myVuex'Vue.use(Vuex)export default new Vuex.Store({state: {name: 'HellowWorld',num:0,age:12},getters:{myName(state){return '姓名:'+ state.name;}},mutations: {addNum(state, payload){state.num += payload;},addAge(state, payload){state.age += payload;}},actions: {asyncAddAge({commit}, payload){setTimeout(()=>{commit('addAge', payload);}, 100);}}
})

myVuex.js

import Vue from 'vue'
// 公共方法
function  forEachValue(obj,fn){Object.keys(obj).forEach(function (key) { return fn(obj[key], key); })
}
// install方法会在外界调用Vue.use的时候执行,
//并且在执行的时候会把Vue实例和一些额外的参数传递出来
const install = (vm, options)=>{vm.mixin({// 在初始化$options之前,给每一个Vue实例添加一个$storebeforeCreate(){// Vue创建实例时会先创建根组件,再创建子组件,if(this.$options && this.$options.store){// 如果根组件,默认就有store,直接赋值即可this.$store = this.$options.store;}else{// 不是根组件,默认没有store,需要把父组件的$store赋值给它this.$store = this.$parent.$store;}}});
}
// 创建一个Store类,即初始结构
class Store {constructor(options){Vue.util.defineReactive(this,'state',options.state)/*this.vm = new Vue({data:{state:options.state}})*/this.wrappedGetters(options);this.wrappedMutations(options)this.wrappedActions(options)}// 自动触发get,转换this.vm,使得获取state时通过`this.$store.state`// 而不是`this.$store.vm.state`获取state// get state(){ return this.vm.state }dispatch = (type, payload)=>{this.actions[type](payload);}wrappedActions(options){let actions = options.actions || {};this.actions = {};forEachValue(actions,(obj,key)=>{this.actions[key] = (payload) =>{obj(this,payload)}})}commit = (type, payload)=>{this.mutations[type](payload)}wrappedMutations(options){let mutations = options.mutations || {};this.mutations = {};forEachValue(mutations,(obj,key)=>{this.mutations[key] = (payload)=>{obj(this.state,payload)}})}wrappedGetters(options){let getters = options.getters || {};this.getters = {};forEachValue(getters,(obj,key)=>{Object.defineProperty(this.getters,key,{get:()=>{return obj(this.state)}})})}}
export default {install,Store
}

module模块化处理

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
为了解决以上问题,Vuex 允许我们将 store 分割成模块(module) 。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割
https://vuex.vuejs.org/zh

命名空间

多个模块中不能出现同名的getters方法

多个模块的mutationsactions中可以出现同名的方法,多个同名的方法不会覆盖, 会放到数组中然后依次执行。

官方代码分析

// 自定义一个方法,根据规定的格式,来创建模块
var Module = function Module (rawModule, runtime) {this.runtime = runtime;this._children = Object.create(null); // 子模块this._rawModule = rawModule; // 当前模块var rawState = rawModule.state; // 当前模块的statethis.state = (typeof rawState === 'function' ? rawState() : rawState) || {};
};
......
var ModuleCollection = function ModuleCollection (rawRootModule) {// 传入模块信息,传入[]空数组,代表第一次传入,还没子模块的传入this.register([], rawRootModule, false);
};
.......

提取模块化结构,实现自己的module

let module = {_rawModule: rootModule,_state: rootModule.state,_children: {}
}

实现module模块化

在myVuex.js新增一个类,单独处理module

class ModuleCollection {constructor(rootModule){this.register([], rootModule);}register(arr, rootModule){// console.log(arr); // [] [home] [login] [login,account]// 1.按照需要的格式创建模块let module = {_rawModule: rootModule,_state: rootModule.state,_children: {}}// 2.保存模块信息if(arr.length === 0){// 保存根模块,第一次传入一个[]空数组,即表示根this.root = module;}else{// 保存子模块,数组不为空,表示有子模块let parent = arr.splice(0, arr.length-1).reduce((root, currentKey)=>{return root._children[currentKey];}, this.root);parent._children[arr[arr.length-1]] = module;}// 3.处理子模块for(let childrenModuleName in rootModule.modules){let childrenModule = rootModule.modules[childrenModuleName];this.register(arr.concat(childrenModuleName) ,childrenModule)}}
}
constructor(options){Vue.util.defineReactive(this,'state',options.state)this.modules = new ModuleCollection(options);this.wrappedModules([], this.modules.root);
}
wrappedModules(arr, rootModule){if(arr.length > 0){let parent = arr.splice(0, arr.length-1).reduce((state, currentKey)=>{return state[currentKey];}, this.state);Vue.set(parent, arr[arr.length-1], rootModule._state);}// 入参由之前的options,变成为现在的模块中的_rawModulethis.wrappedGetters(rootModule._rawModule);this.wrappedMutations(rootModule._rawModule)this.wrappedActions(rootModule._rawModule)// 如果当前不是子模块, 那么就需要从根模块中取出子模块的信息for(let childrenModuleName in rootModule._children){let  childrenModule = rootModule._children[childrenModuleName];this.wrappedModules(arr.concat(childrenModuleName), childrenModule);}
}

上面说到多个模块的mutationsactions中可以出现同名的方法

处理mutationsactions的同名方法,push到数组,逐个执行

dispatch = (type, payload)=>{this.actions[type].forEach(fn=>fn(payload));
}
wrappedActions(options){let actions = options.actions || {};this.actions = this.actions || {};for(let key in actions){this.actions[key] = this.actions[key] || [];this.actions[key].push((payload)=>{actions[key](this, payload);});}
}
commit = (type, payload)=>{this.mutations[type].forEach(fn=>fn(payload));
}
wrappedMutations(options){let mutations = options.mutations || {};this.mutations = this.mutations || {};for(let key in mutations){this.mutations[key] = this.mutations[key] || [];this.mutations[key].push((payload)=>{mutations[key](options.state, payload);});}
}

完整示例二

myVuex.js

import Vue from 'vue'
// 公共方法
function  forEachValue(obj,fn){Object.keys(obj).forEach(function (key) { return fn(obj[key], key); })
}
// install方法会在外界调用Vue.use的时候执行,
//并且在执行的时候会把Vue实例和一些额外的参数传递出来
const install = (vm, options)=>{vm.mixin({// 在初始化$options之前,给每一个Vue实例添加一个$storebeforeCreate(){// Vue创建实例时会先创建根组件,再创建子组件,if(this.$options && this.$options.store){// 如果根组件,默认就有store,直接赋值即可this.$store = this.$options.store;}else{// 不是根组件,默认没有store,需要把父组件的$store赋值给它this.$store = this.$parent.$store;}}});
}
class ModuleCollection {constructor(rootModule){this.register([], rootModule);}register(arr, rootModule){console.log(arr)// 1.按照我们需要的格式创建模块let module = {_rawModule: rootModule,_state: rootModule.state,_children: {}}// 2.保存模块信息if(arr.length === 0){// 保存根模块,第一次传入一个[]空数组,即表示根this.root = module;}else{// 保存子模块,数组不为空,表示有子模块let parent = arr.splice(0, arr.length-1).reduce((root, currentKey)=>{return root._children[currentKey];}, this.root);console.log(parent);parent._children[arr[arr.length-1]] = module;}// // 3.处理子模块for(let childrenModuleName in rootModule.modules){let childrenModule = rootModule.modules[childrenModuleName];this.register(arr.concat(childrenModuleName) ,childrenModule)}}
}
// 创建一个Store类,即初始结构
class Store {constructor(options){Vue.util.defineReactive(this,'state',options.state)/*this.vm = new Vue({data:{state:options.state}})*/this.modules = new ModuleCollection(options);this.wrappedModules([], this.modules.root);}wrappedModules(arr, rootModule){if(arr.length > 0){let parent = arr.splice(0, arr.length-1).reduce((state, currentKey)=>{return state[currentKey];}, this.state);Vue.set(parent, arr[arr.length-1], rootModule._state);}this.wrappedGetters(rootModule._rawModule);this.wrappedMutations(rootModule._rawModule)this.wrappedActions(rootModule._rawModule)// 如果当前不是子模块, 那么就需要从根模块中取出子模块的信息来安装for(let childrenModuleName in rootModule._children){let  childrenModule = rootModule._children[childrenModuleName];this.wrappedModules(arr.concat(childrenModuleName), childrenModule);}}// 自动触发get,转换this.vm,使得获取state时通过`this.$store.state`// 而不是`this.$store.vm.state`获取state// get state(){ return this.vm.state }dispatch = (type, payload)=>{this.actions[type].forEach(fn=>fn(payload));}wrappedActions(options){let actions = options.actions || {};this.actions = this.actions||{};forEachValue(actions,(obj,key)=>{this.actions[key] = this.actions[key] || [];this.actions[key].push((payload)=>{obj(this, payload);});})}commit = (type, payload)=>{this.mutations[type].forEach(fn=>fn(payload));}wrappedMutations(options){let mutations = options.mutations || {};this.mutations = this.mutations || {};forEachValue(mutations,(obj,key)=>{this.mutations[key] = this.mutations[key] || [];this.mutations[key].push((payload)=>{obj(options.state, payload);});})}wrappedGetters(options){let getters = options.getters || {};this.getters = this.getters || {};forEachValue(getters,(obj,key)=>{Object.defineProperty(this.getters,key,{get:()=>{return obj(this.state)}})})}}
export default {install,Store
}

store/index.js

import Vue from 'vue'
// import Vuex from 'vuex'
import Vuex from './myVuex'Vue.use(Vuex)
let home = {state: {name: 'home',globalNum:10},getters: {// 多个模块中不能出现同名的getters方法getHomeName(state){return state.name + '子模块Home';},},mutations: {changeHomeName(state, payload){state.name += payload;},// 多个模块的mutations中可以出现同名的方法// 多个同名的方法不会覆盖, 会放到数组中然后依次执行changeGlobalNum(state, payload){state.globalNum += payload;}},actions: {// 多个模块的actions中可以出现同名的方法// 多个同名的方法不会覆盖, 会放到数组中然后依次执行asyncChangeGlobalNum({commit}, payload){console.log('home中的asyncChangeGlobalNum');setTimeout(()=>{commit('changeGlobalNum', payload);}, 100);}}
}
let account = {state: {name: 'account',globalNum:10},getters: {// 多个模块中不能出现同名的getters方法getAccountName(state){return state.name + '子模块Account';},},mutations: {changeAccountName(state, payload){state.name += payload;},// 多个模块的mutations中可以出现同名的方法// 多个同名的方法不会覆盖, 会放到数组中然后依次执行changeGlobalNum(state, payload){state.globalNum += payload;}},actions: {// 多个模块的actions中可以出现同名的方法// 多个同名的方法不会覆盖, 会放到数组中然后依次执行asyncChangeGlobalNum({commit}, payload){console.log('Account中的asyncChangeGlobalNum');setTimeout(()=>{commit('changeGlobalNum', payload);}, 100);}}
}
let login = {state: {name: 'login',globalNum:10},getters: {// 多个模块中不能出现同名的getters方法getLoginName(state){return state.name + '子模块Login';},},mutations: {changeLoginName(state, payload){state.name += payload;},// 多个模块的mutations中可以出现同名的方法// 多个同名的方法不会覆盖, 会放到数组中然后依次执行changeGlobalNum(state, payload){state.globalNum += payload;}},actions: {// 多个模块的actions中可以出现同名的方法// 多个同名的方法不会覆盖, 会放到数组中然后依次执行asyncChangeGlobalNum({commit}, payload){console.log('Login中的asyncChangeGlobalNum');setTimeout(()=>{commit('changeGlobalNum', payload);}, 100);}},modules: {account:account}
}
export default new Vuex.Store({// 用于保存全局共享数据state: {name: 'HelloWorld',globalNum:10,num:0,age:12},getters:{myName(state){return state.name + '111';}},// 用于同步修改共享数据mutations: {addNum(state, payload){state.num += payload;},addAge(state, payload){state.age += payload;},changeGlobalName(state, payload){state.globalNum += payload;}},// 用于异步修改共享数据actions: {asyncChangeGlobalNum({commit}, payload){console.log('全局中的asyncChangeGlobalNum');setTimeout(()=>{commit('changeGlobalNum', payload);}, 100);}},// 用户模块化共享数据modules: {home:home,login:login}
})

App.vue


相关内容

热门资讯

装载8000万桶原油的超级油轮... 6月19日,财闻海外资讯消息,载有近8000万桶石油的超级油轮正停泊在波斯湾,一旦交易商和船东发出指...
原创 刚... 法国总统马克龙最近的状态,用一句哭笑不得来形容再贴切不过。原本他一门心思准备在对华贸易议题上做文章,...
惠誉:将宁德时代的发行人主体评... 6月18日,惠誉国际评级有限公司(下称“惠誉”)上调宁德时代(300750.SZ/03750.HK)...
临商银行“临商红”青年志愿服务... 为大力弘扬践行沂蒙精神,临商银行联合市委金融工委、市委市直机关工委、共青团临沂市委共同打造了“临商红...
SpaceX 上市:SPCX ... EBC Financial Group 自开盘起即向全球交易者提供双向交易通道,参与这一史上最大规模...
甘肃电气集团长开公司荣获202... 近日,在2026年度中国中压电器行业权威评选活动中,甘肃电气集团长开公司荣获中国中压电器市场“卓越贡...
是80%的工位面向海景,马岩松... 腾讯总部园区 摄影:张超 深圳大铲湾,腾讯总部园区“企鹅岛”于5月底首次面向公众开放。 三座由马岩松...
日本经济专家:加息难以扭转日元... 日本央行近日宣布将政策利率自0.75%上调至1.0%,为31年来最高水平。日本经济专家认为,目前日元...
黄金跌2%失守4130美元,白... 6月19日午间,黄金白银仍未止跌。截至13时,现货黄金跌2%,报4124.57美元/盎司,失守413...
原创 中... 2026年6月这个节点意味格外不同。4月伊朗与以色列那场脆弱的停火刚撑了不到两个月,6月8日两边又对...
Manus回购方案浮出水面:中... 文 | 强调Next 据外媒The Information6月18日报道,Manus的早期中国投资...
2026黄金回收避坑,郑州72... 来源:黄冈新闻网 一、郑州黄金回收市场现状与高价引流投诉占比 据郑州市 12315 消费维权平台 2...
原创 房... 2026年6月16日国家统计局公布的5月份70城房价数据显示,一线城市新建商品住宅环比上涨,二三线城...
“生活成本太高了!”全球多地年... “白天当会计,晚上跑网约车,周末送外卖,假期摆地摊……” 如今,这种“1+N”(“主业+副业”)的多...
字节之后,天数智芯的下一个客户... 今年预计交付5万片,销售部门或迎来一轮调整|图源:豆包AI 作者/ IT时报 毛宇 编辑/ 郝俊慧 ...
聚焦尾货赛道!佛山(高明)“泛... 6月18日下午,佛山(高明)“泛家居国际尾货直播基地”项目合作签约仪式在高明鸿创数字科技产业园举行。...
流量退潮,文娱市场终于“醒”了 近段时间,一场场舆论风波在文娱市场激起千层浪。老牌综艺《奔跑吧》深陷口碑泥潭,常驻嘉宾白鹿被“考古”...
原创 世... 三十年前的世界五百强榜单,中国只有3家企业上榜,隔壁日本整整149家,美国151家。那时候的世界五百...
第四套人民币小全套伍元、贰元、... 第四套人民币小全套伍元、贰元、壹圆纸币收藏鉴赏 第四套人民币是我国改革开放时代的标志性货币,承载了八...
感觉血糖还行就没事?不重视这五... 很多糖友觉得,只要平时测的血糖值看着“还行”,身体也没什么不舒服,就万事大吉了。 这是一个要命的误区...