当前位置: 首页 > Web前端 > vue.js

@ice-stark-data源码分析

时间:2023-03-31 23:08:35 vue.js

在基于qiankun开发微前端应用的过程中,难免会有应用之间的通信,比如一些基于某种格式的自定义业务需求。这时候就涉及到某个子应用需要知道当前“宿主”是谁(标识是哪个私有化项目定制的业务需求),而要知道“宿主”是谁,“宿主”需要主动通知某个子应用,这涉及到主应用和子应用之间的数据通信。微前端下应用之间的通信方式基于qiankun框架自带的通信方式:通过api中的initGlobalState进行通信基于第三方库的通信方式:如@ice/stark-dataforcommunication基于qiankun自带的通信方式基于@ice/stark-data的通信方式是基于@ice/stark-data的微前端架构模式。可以实现主应用和子应用之间的通信方式(全双工),表现如下:main->sub:对于这个场景,可以使用store对象的set和get方法@/ice/stark-datalibrarytoimplementsub->main:对于这种场景,可以使用@/ice/stark-datalibrary中的emit,event对象实现sub->sub:Forthisscenario,可以使用@/ice/stark-data库中store对象的set和get方法来实现@ice/stark-data源码分析我们知道,基于VueJs或ReactJs等三方状态管理库vuex或redux将数据存储在内存中(非持久性)。同样,@ice/stark-data在存储数据时,是基于一定的命名空间结合window对象存储的,也是非持久化的。但是@ice/startk-data实现了一个简单的发布-订阅机制,通过全局窗口在应用程序之间共享数据。一般来说,内容会比较简单,vuex和redux都是状态管理方案。使用场景不同。注:目前解析的源码版本为0.1.3,仓库地址:https://github.com/ice-lab/ic...整个@ice/stark-data库的源码其实比较简单,由以下几部分组成:utils.ts:工具集,一共包括isObject、isArray、warn三个函数,分别用于判断一个变量是否为对象,数组类型,以及输出警告信息的函数包.cache.ts:命名空间ICESTARK和window全局对象封装的访问函数setCache和getCache。这里使用命名空间,也可以在一定程度上避免变量对window全局对象的污染。store.ts:主要实现主应用与子应用、子应用与子应用event之间的单向数据通信。ts:主要实现子应用与主应用之间的单向数据通信index.ts:导出store而eventondemand可以看出整个库中的核心代码都在store.ts和event.ts文件中,接下来具体分析这两个文件中的代码。store.ts源码分析当我们需要从主应用向子应用传输数据时,一般基于@ice/stark-data的做法如下:主应用设置数据import{store}from'@ice/stark-data'store.set('someData','darkCode')子应用程序从'@ice/stark-data'constdata:any=store.get('someData')instore.ts接收数据import{store}。在第11行和第14行之间,定义了一个名为IO的接口。并在该接口中分别定义set和get方法:interfaceIO{set(key:string|symbol|object,value?:any):void;get(key?:StringSymbolUnion):void;}其中set方法接收两个参数,key的类型为联合类型,value为任意类型的变量。该方法没有返回值。get方法接收一个参数,key的类型为联合类型。这个方法也没有返回值?在第16行和第20行之间,定义了一个名为Hooks的接口。并且在这个接口中,分别定义了on、off、has三个方法:off(key:StringSymbolUnion,callback?:(value:any)=>void):void;has(key:StringSymbolUnion):boolean;}接口Hooks的主要作用是订阅和发布数据以及销毁对应的“事件”处理。在代码的第22行,定义了Store类,它实现了两者同时有IO和Hooks的接口,类Store实现了IO,Hooks在类Store中分别定义了store和storeEmitter两个属性,并在构造函数中初始化:store:object;storeEmitter:object;constructor(){this.store={};this.storeEmitter={};}接下来,定义了两个“私有”方法,_setValue和_getValue分别“写入”和“输出”“数据”。_getValue(key:StringSymbolUnion){returnthis.store[key];}_setValue(key:StringSymbolUnion,value:any){this.store[key]=value;this._emit(key);}在_setValue的实现中,首先在实例对象属性store对象上挂载key属性,并设置其值为value.同时从_get中获取对应的key通过调用_emit方法取值,并从属性storeEmitter中获取对应的“触发器”(keyEmitter),然后对其遍历执行对应的回调。接下来就是重写IO接口实现中的set和get方法。先看set方法的实现(67~84行):set(key:string|symbol|object,value?:T){if(typeofkey!=='string'&&typeofkey!=='symbol'&&!isObject(key)){warn('store.set:keyshouldbestring/symbol/object');返回;}if(isObject(key)){Object.keys(key).forEach(k=>{constv=key[k];this._setValue(k,v);});}else{this._setValue(keyasStringSymbolUnion,value);}}内部先判断参数key变量的类型,如果不是string、symbol、object类型中的一种,则返回它们之间。反之,先判断key变量是否为object类型,然后获取key“object”中的“key”并遍历。遍历过程中得到k对应的v。调用实例对象的内部方法_setValue来存储数据(值);如果key不是对象类型,则调用实例对象的内部方法_setValue来存储数据(值)。get方法是通过key获取对应存储的数据(value),首先判断参数key的类型,如果不是string和symbol中的一种,则返回null。反之调用内部方法_getValue获取值。接下来就是重写接口Hooks中的三个方法。先看on(从86行到106行)的第一个方法:on(key:StringSymbolUnion,callback:(value:any)=>void,force?:boolean){if(typeofkey!=='string'&&typeofkey!=='symbol'){warn('store.on:keyshouldbestring/symbol');返回;}if(callback===undefined||typeofcallback!=='function'){warn('store.on:callbackisrequired,shouldbefunction');返回;}if(!this.storeEmitter[key]){this.storeEmitter[key]=[];}this.storeEmitter[key].push(回调);如果(强制){回调(this._getValue(键));on方法接收三个参数:key:参数key为字符串和符号的联合类型callback:参数callback为回调函数force:参数force为可选参数,类型为boolean类型。从源码实现可以看出,on方法的主要作用是根据key和callback参数将元素存储到storeEmitter中。如果参数force为true,则通过参数key从方法_getValue中获取对应的值作为回调函数callback的参数,执行回调函数callback。对于第二种方法off(108到125行),实现如下:off(key:StringSymbolUnion,callback?:(value:any)=>void){if(typeofkey!=='string'&&typeofkey!=='symbol'){warn('store.off:keyshouldbestring/symbol');返回;}if(!isArray(this.storeEmitter[key])){warn(`store.off:${String(key)}没有回调`);返回;}if(callback===undefined){this.storeEmitter[key]=undefined;返回;}this.storeEmitter[key]=this.storeEmitter[key].filter(cb=>cb!==callback);}off方法接收两个参数:key:参数key为字符串和符号的联合类型callback:参数callback为回调函数从源码实现可以看出off方法的主要函数是根据实例对象的storeEmitter属性结合key过滤掉回调(类似于从数组中删除一个元素),然后调用getCache函数创建一个变量store。如果变量store没有值,则调用类Store创建一个实例对象给store变量赋值,并将该变量挂载到以storeNameSpace为命名空间的window对象。最后导出商店变量。让store=getCache(storeNameSpace);if(!store){store=newStore();setCache(storeNameSpace,store);}导出默认存储;总结:当我们在设置某个值时通过键值对(key,value)调用store.set方法时,内部会先判断key的类型,如果key是对象类型。然后使用Object.keys方法获取对象上的所有属性,遍历属性集合得到属性(k)对应的值(v),然后调用store实例内部的_setValue方法设置值相应属性的(k)(v)赋值给实例存储属性。然后调用内部方法_emit方法从实例属性storeEmitter中获取对应的值(keyEmitter,value),根据属性k调用内部方法_getValue。最后遍历数组keyEmitter中的每一个元素(回调函数),以value为参数执行其回调函数。在库的实现过程中,命名空间的使用旨在隔离(直接)造成窗口对象属性的污染。当我们调用store.get方法通过key|获取对应的value时property(key),内部会先判断key是否存在,不存在则返回实例对象的store属性;如果键存在,但其类型不是字符串/符号之一,则返回null。反之,调用实例内部的_getValue方法,通过属性键从实例属性存储中获取对应的值。这里,Store类中的store和storeEmitter这两个属性在其定义和取值操作中都涉及到了对队列的操作。这两个属性的类型都是“对象”类型,但也可以定义为数组类型(从实现的角色来说)或者通过WeakMap来处理会更好。event.ts源码分析基于@ice/stark-data从子应用向主应用传输数据时,方法一般如下:sub-application:import{event}from'@ice/stark-data'event.emit('refreshToken','cdacavfsasxxs')mainapplicationimport{event}from'@ice/stark-data'event.on('refreshToken',(val:any)=>{console.log('thevaluefromsubAppis:',val)})这个实现是通过发布-订阅模型实现的,类似于VueJs中子组件和父组件的通信。接下来从源码的角度去了解它们内部的实现细节。在store.ts代码中,第六行代码定义了一个常量eventNameSpace命名空间consteventNameSpace='event';,第八行代码定义了一个联合类型StringSymbolUnion(类型定义),typeStringSymbolUnion=string|symbol;然后在10到15行定义了接口Hooks,里面定义了4个方法,分别是:emit:emit(key:StringSymbolUnion,value:any):void;该方法在实现阶段的作用是对于订阅“事件”,从队列中遍历所有“事件”,并执行相应的回调。on:on(key:StringSymbolUnion,callback:(value:any)=>void):void;该方法在实现阶段的作用是将回调函数callback存放在off队列中:off(key:StringSymbolUnion,callback?:(value:any)=>void):void;该方法在实现层面的作用是从队列中寻找不属于回调的元素并进行移除(过滤)操作has:has(key:StringSymbolUnion):boolean;该方法实现阶段的作用是根据某个key判断队列中设置的对应“值”是否存在,返回一个布尔值。event.ts文件中的第17行定义了类Event并实现了Hooks接口类Event实现了Hooks。属性eventEmitter在类Event中定义并在构造函数中初始化。事件发射器:对象;constructor(){this.eventEmitter={};}接下来就是重写并实现接口Hooks中定义的四个方法。先看on方法的实现:on(key:StringSymbolUnion,callback:(value:any)=>void){if(typeofkey!=='string'&&typeofkey!=='symbol'){warn('event.on:key应该是字符串/符号');返回;}if(callback===undefined||typeofcallback!=='function'){warn('event.on:callbackisrequired,shouldbefunction');返回;}if(!this.eventEmitter[key]){this.eventEmitter[key]=[];}this.eventEmitter[key].push(callback);on方法的实现从源码可以看出其实也很简单,和store.ts代码中类Store中on方法的实现几乎一样。这里我就不细说了。至于其他三个方法的实现,和上面说的Store类中相应的三个方法的实现几乎是一样的,我就不细说了。最后调用函数getCache创建一个对象类型的变量事件,然后事件不存在(值为null、undefined等)。将通过new关键字调用类Event创建的对象实例赋值给变量event,同时调用setCache函数根据命名空间常量eventNameSpace将变量event挂载到window对象上。然后导出变量事件(对象类型)。总结基于微前端框架qiankun中的@ice/stark-data,实现主子应用全双工通信非常简单。核心是基于发布-订阅模型,使用不同的命名空间变量作为区分。将对应属性(key)的值(value)挂载到全局window对象上,这样在同一个“应用”中,只要知道对应的命名空间,就可以访问到对应的值;@ice/stark-数据源代码的封装存在一些不足。比如store.ts和event.ts中定义的Hooks接口,并没有达到复用的效果(各定义一次,无必要)。另外,store.ts和event.ts分别实现类Store和Event定义中相同的部分方法代码。没有必要每一个都实施一次。您可以编写一个基类(父类),然后从它继承扩展。.重写需要重写的方法会好很多;源码中代码健壮性的处理还是不错的,比如类Event中实现的四个方法中key的判断和处理。