场景
- 在业务上现在有一个场景,当发生业务行为变化时,需要对各个模块的行为进行数据收集,数据用途可以用作回顾,也可以是例如监控这样的场景。
核心问题
- 说白了这个需求就是需要对各个模块状态变更进行记录,然后再格式化上传到服务端。
- 解题思路有两种一种是状态监听,第二主动收集。
状态监听
状态监听优势
- 快速实现利用状态管理和wacth的机制很快就知道不同模块的状态变更,然后就可以获取数据,再格式化数据,发送给服务端
状态监听劣势
- wacth的重复监听,只要使用了wacth,不管是不是你所需要的数据,只要状态变更就会触发改变,监听行为
- 重复依赖,比如说全局有个开始结束的状态,在使用wacth的时候就需要在不同的wacth中都去判断这个状态,或者有全局的时间模块等等
- 重复书写,在不同的监听中需要实践相同的数据格式化方法
- 数据分布混乱,虽然控制了全局使用同一管道上传,但是对于同一个管道内的数据想做合并去重,或者其他自定义的操作,在不同类型数据,同一管道的这个场景下面支持很弱
- 场景区分困难,正常流程触发的监听是没有问题,如果是异常场景触发恢复的监听就会导致判断的复杂性
- 描述的还是比较抽象看下代码示例
function useA(){
wacth(new,old){
if(start){
if(new.type =='need')
const a = {
a:new.a
}
const aa = {
aa:new.aa
}
upload(a)
upload(aa)
}
}
}
function useB(){
wacth(new,old){
if(start){
if(new.type =='need')
const b = {
b:new.b
}
const aa = {
b:new.aa
}
upload(b)
upload(aa)
}
}
}
重构实现思路
- 依赖收集(监听者模式)
- 状态统一
- 数据自治(策略模式)
依赖收集
- 核心思想:希望使用同一个采集器解决整个业务流程,数据变更在各个变更方,通过采集器提供的标准的格式化方法去处理数据,再把数据传递到采集器,采集器收到数据后根据不同的数据格式插入到不同的缓存通道,缓存通道缓存成功,通知业务处理的监听者,根据不同的数据类型进行不同的处理方式,最后发送到服务端。
- 具体代码如下
class Dep {
private subs: any = []
public addSub(sub: any) {
if (sub && sub.update) {
this.subs.push(sub)
}
}
public notify(content: any) {
this.subs.forEach((sub: any) => {
sub.update(content)
})
}
}
class Watcher {
private cb!: (arg: any) => void
constructor(cb: (arg: any) => void) {
this.cb = cb
}
update(content: any) {
this.cb(content)
}
}
class Channel {
private queue: any = []
private limitSize = 1
public name: string
constructor(name: string, limitSize = 1) {
this.name = name
limitSize = limitSize >= 1 ? limitSize : 1
this.limitSize = limitSize
}
push(item: any) {
if (this.limitSize == this.queue.length) {
this.queue.shift()
}
this.queue.push(item)
}
getLast() {
if (this.queue.length > 0) {
return this.queue[this.queue.length - 1]
} else {
throw new Error('no item return')
}
}
getLastIndex(index: number) {
try {
return this.queue[this.queue.length - index - 1]
} catch (error) {
throw new Error('no item return')
}
}
isEmpty() {
return this.queue.length == 0
}
}
export class Collection {
private dep = new Dep()
private dataQueue = ['A', 'B', 'C']
private channelMap = new Map()
private MQ!: LiveCollectionMQ
private strategies = {
A: () => {
},
B: () => {
},
C: () => {
},
} as Record<NotifyType, any>
constructor() {
this.init()
}
private init() {
this.intWatcher()
this.initChannel()
this.initData()
this.initMQ()
}
private intWatcher() {
this.dep.addSub(
new Watcher((type: NotifyType) => {
const handlerBack = this.getHandler(type)
handlerBack()
}),
)
}
private initChannel() {
this.dataQueue.forEach(item => {
this.channelMap.set(item, new Channel(item, 3))
})
}
private initData() {
}
private initMQ() {
}
public getMQ() {
return this.MQ
}
private getChannel(name: NotifyType) {
if (this.channelMap.get(name)) {
return this.channelMap.get(name)
} else {
throw new Error('no channel')
}
}
public notify(type: NotifyType, mes: any) {
this.setChannel(type, mes)
if (state.value.type) {
this.dep.notify(type)
}
}
private setChannel(name: NotifyType, mes: any) {
const channel = this.getChannel(name)
channel.push(mes)
}
private getHandler(name: NotifyType) {
return this.strategies[name]
}
public getChannelLast(name: NotifyType) {
try {
const channel = this.getChannel(name)
return channel.getLast()
} catch (error) {
throw new Error(error)
}
}
public getChannelItemByLastIndex(name: NotifyType, index: number) {
try {
const channel = this.getChannel(name)
return channel.getLastIndex(index)
} catch (error) {
throw new Error(error)
}
}
public generateA() {
}
public generateB() {
}
public generateC() {
}
}
export const CollectionHelper = new Collection()
总结
- 我觉得去了解一个框架的一个好的思路就是在运用它的核心原理去解决一个原理,正如之前使用webpack的插件机制一样,这次使用的是vue的依赖收集
- 状态自治,职责统一是个代码封装的好习惯