Typescript的装饰器实现优雅的编写MVC Control类
2018-11-20 03:26
使用MVC框架时,为了降低视图与模型之间的耦合性,会在Control中编写与View、Model交互的逻辑,当然每个人的MVC实现方式可能都有所不同,但大体还是如此。本篇文章编写MVC将是View、Model以事件调用Control交互为模型,在Typescript的装饰器的辅助下编写一些使编写Control逻辑更舒适的代码。
环境 1.Windows 10 2.Typescript 3.1
装饰器 这里可能需要对Typescript中的装饰有一些小小的了解,如果你不是很清楚可以通过这里 先了解一下再看也不迟。
代码 下面编写一些基础的逻辑和类型声明,将使用ControlManager.model_msg_mgr用于相关的对象数据缓存。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 // ConstrolBase.ts class ControlManager { static model_msg_mgr = {}; static getModelEvents(pt): { [e: number]: Function[] } { if (ControlManager.model_msg_mgr[pt]) return ControlManager.model_msg_mgr[pt]; return {}; } static setModelEvents(pt, e: number, cb: Function) { if (ControlManager.model_msg_mgr[pt] === undefined) ControlManager.model_msg_mgr[pt] = {}; if (ControlManager.model_msg_mgr[pt][e] === undefined) ControlManager.model_msg_mgr[pt][e] = []; ControlManager.model_msg_mgr[pt][e].push(cb); } } /** 控制器原形 */ class ControlBase { protected msgMap = {}; protected View: any; protected Model: any; constructor() { } public register(msg: number, callback: Function) { this.msgMap[msg] = callback; } public trigger(msg: number, ...params: any[]) { if (this.msgMap[msg]) this.msgMap[msg].call(this, ...params) } public unregister(msg: number) { if (this.msgMap[msg]) delete this.msgMap[msg] } }
这里则编写一些逻辑,用于将一些数据储存到ControlManager
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 // Decorate.ts /** 注册 */ function control_register(target: any) { /** 原形 */ const original = target; const newCls: any = function (...args) { const cls: any = function () { return original.call(this, ...args) } cls.prototype = original.prototype; let instance = new cls(); let em = ControlManager.getModelEvents(cls.prototype); for (const e in em) { em[e].forEach((cb) => instance.register(parseInt(e), cb)); } return instance } return newCls; } /** 事件注册 */ function control_event_register(msg) { return function (pt, name, desc) { ControlManager.setModelEvents(pt, msg, desc.value); } }
下面粗略的实现BagControl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 // BagControl.ts /** 控制器消息 */ enum BagConst { Buy = 1000, Sell } @control_register class BagControl extends ControlBase { constructor() { super(); } @control_event_register(BagConst.Buy) private buy(id: string, count: number) { // ...操作视图或者模型 console.log(`你购买${count}个${id}.`); } @control_event_register(BagConst.Sell) private sell(id: string, count: number) { // ...操作视图或者模型 console.log(`你出售${count}个${id}.`); } }
这个例子,我们会测试BagConst.Buy与BagConst.Sell消息是否成功的注册了。
1 2 3 let model = new BagControl(); model.trigger(BagConst.Buy, "苹果", 10); model.trigger(BagConst.Sell, "梨", 10);
这样使用的话会比在constructor中重复的编码调用this.register(…)好很多。但实际上还是有一些问题的,例如无法准确的控制注册的顺序以及更改相关事件的调用顺序流程时,使用这种方式就会显得有些糟糕了。并且有一个致命的缺点就是在使用model instanceof BagControl将不能得到你想看到的结果。
后记 这只是我突发奇想而编写的,并没有进行过专业严格的测试,不确定是否能应用于项目之中,使用需要三思。当然你或是有什么好的方式或是意见也可以邮件给我交流讨论。