Closed Hilshire closed 4 years ago
为了方便阅读,下文将 混入的类称为mixin,将混入的目标类称为target
mixin
target
虽然饱受争议,但是 js 的原型继承确实有一些优势。比如说,它可以非常方便地实现混入。ts作为 js 的方言,支持混入是理所当然的。但是 Ts 却似乎在这里遇到了难题。下面的代码来自官方文档 mixins
// Disposable Mixin class Disposable { isDisposed: boolean; dispose() { this.isDisposed = true; } } // Activatable Mixin class Activatable { isActive: boolean; activate() { this.isActive = true; } deactivate() { this.isActive = false; } } class SmartObject implements Disposable, Activatable { constructor() { setInterval(() => console.log(this.isActive + " : " + this.isDisposed), 500); } interact() { this.activate(); } // Disposable isDisposed: boolean = false; dispose: () => void; // Activatable isActive: boolean = false; activate: () => void; deactivate: () => void; } applyMixins(SmartObject, [Disposable, Activatable]); let smartObj = new SmartObject(); setTimeout(() => smartObj.interact(), 1000); //////////////////////////////////////// // In your runtime library somewhere //////////////////////////////////////// function applyMixins(derivedCtor: any, baseCtors: any[]) { baseCtors.forEach(baseCtor => { Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => { Object.defineProperty(derivedCtor.prototype, name, Object.getOwnPropertyDescriptor(baseCtor.prototype, name)); }); }); }
这可能是你所能想到的最不优雅的实现了。它依靠一个自定义的 applyMixins 函数,而这个函数则把 javascript 的内部机制暴露无遗。显然不是每个人都喜欢这种做法。于是我们就有了这些:
applyMixins
Advanced Mixins on Typescript
Mixin Classes in TypeScript
The mixin pattern in TypeScript – all you need to know
而这里面提出的方法大致是相同的。但是,为了理解其中的用法,我们需要了解 Ts 究竟遇到了什么困难
我们使用混入的目的在于:
第二是混入的重点。如果我们只需要混入一段代码,只需要把需要将 mixin 作为父类即可。
第三点则比较微妙:使用普通的继承,我们也可以实现这一点。但是这样的写法是非常脆弱的,这意味着父类知道子类里有什么。下面的代码的问题显而易见,没人会用这种方式进行继承。
// Bad class Base { speak() { console.log(this.name) } } class Man extends Base { constructor(name) { super(name) this.name = name } } const man = new Man('tom') man.speak() // tom
而在 ts 中,使用 js 风格的混入会报错,因为被混入的类并不会有目标类的属性声明。对于强类型语言这可以说是理所当然的。
而第三点的问题很容易解决。我们可以声明一个 base 类,按照 base -> mixin -> target 的方式继承。这样从语义上来说甚至更清晰一点。但是,如何方便的混入多个 mixin 就有点让人头疼了。
base
base -> mixin -> target
class Point { constructor(public x: number, public y: number) {} } class Person { constructor(public name: string) {} } // mixin 部分开始 // 构造函数类型 type Constructor<T> = new(...args: any[]) => T; // 包装函数,返回一个子类 // 这里 Constructor 的泛型,可以约束 mixins 扩展的对象 function Tagged<T extends Constructor<{}>>(Base: T) { return class extends Base { _tag: string; constructor(...args: any[]) { super(...args); this._tag = ""; } } } // mixin 结束。现在我们有了一个包装函数可以用来混入了。 // 就像下面这样 const TaggedPoint = Tagged(Point); let point = new TaggedPoint(10, 20); point._tag = "hello"; class Customer extends Tagged(Person) { accountBalance: number; } let customer = new Customer("Joe"); customer._tag = "test"; customer.accountBalance = 0;
简单来说,我们写了一个包装函数,这个函数会创建 mixin,mixin 会继承 base。当我们需要混入的时候,就使用包装函数包装一下,它将返回 mixin 的子类,也就是我们要的 target 。
那么,加入我们需要混入多个 mixin 呢?
// 是的,Activatable, Tagged, Timestamped 都是 mixin const SpecialUser = Activatable(Tagged(Timestamped(User))); const user = new SpecialUser("John Doe");
为了方便阅读,下文将 混入的类称为
mixin
,将混入的目标类称为target
虽然饱受争议,但是 js 的原型继承确实有一些优势。比如说,它可以非常方便地实现混入。ts作为 js 的方言,支持混入是理所当然的。但是 Ts 却似乎在这里遇到了难题。下面的代码来自官方文档 mixins
这可能是你所能想到的最不优雅的实现了。它依靠一个自定义的
applyMixins
函数,而这个函数则把 javascript 的内部机制暴露无遗。显然不是每个人都喜欢这种做法。于是我们就有了这些:Advanced Mixins on Typescript
Mixin Classes in TypeScript
The mixin pattern in TypeScript – all you need to know
而这里面提出的方法大致是相同的。但是,为了理解其中的用法,我们需要了解 Ts 究竟遇到了什么困难
为什么需要混入?
我们使用混入的目的在于:
第二是混入的重点。如果我们只需要混入一段代码,只需要把需要将
mixin
作为父类即可。第三点则比较微妙:使用普通的继承,我们也可以实现这一点。但是这样的写法是非常脆弱的,这意味着父类知道子类里有什么。下面的代码的问题显而易见,没人会用这种方式进行继承。
而在 ts 中,使用 js 风格的混入会报错,因为被混入的类并不会有目标类的属性声明。对于强类型语言这可以说是理所当然的。
而第三点的问题很容易解决。我们可以声明一个
base
类,按照base -> mixin -> target
的方式继承。这样从语义上来说甚至更清晰一点。但是,如何方便的混入多个mixin
就有点让人头疼了。所以,解决的方法是什么呢?
简单来说,我们写了一个包装函数,这个函数会创建
mixin
,mixin
会继承base
。当我们需要混入的时候,就使用包装函数包装一下,它将返回mixin
的子类,也就是我们要的target
。那么,加入我们需要混入多个
mixin
呢?