TypeScript 中的 Mixin
Posted
技术标签:
【中文标题】TypeScript 中的 Mixin【英文标题】:Mixins in TypeScript 【发布时间】:2012-09-25 01:13:32 【问题描述】:我正在玩 TypeScript,我有几个 functional mixins、Eventable
和 Settable
,我想将它们混入 Model
类(假设它类似于 Backbone .js 模型):
function asSettable()
this.get = function(key: string)
return this[key];
;
this.set = function(key: string, value)
this[key] = value;
return this;
;
function asEventable()
this.on = function(name: string, callback)
this._events = this._events || ;
this._events[name] = callback;
;
this.trigger = function(name: string)
this._events[name].call(this);
class Model
constructor (properties = )
;
asSettable.call(Model.prototype);
asEventable.call(Model.prototype);
上面的代码运行良好,但如果我尝试使用(new Model()).set('foo', 'bar')
等混合方法之一,则无法编译。
我可以解决这个问题
-
为 mixins 添加
interface
声明
在Model
声明中声明虚拟get
/set
/on
/trigger
方法
有没有绕过虚拟声明的干净方法?
【问题讨论】:
可能与Microsoft/TypeScript#2919相关的解决方案@ 【参考方案1】:这是使用interfaces
和static create()
方法来处理mixin 的一种方法。接口支持多重继承,这样您就不必为您的 mixins 重新定义 interfaces
,而 static create()
方法负责将 Model()
的实例返回为 IModel
(需要 <any>
演员表)禁止编译器警告。)您需要在IModel
上复制Model
的所有成员定义,这很糟糕,但它似乎是在当前版本的TypeScript 中实现您想要的最干净的方法。
编辑:我已经确定了一种稍微简单的方法来支持 mixin,甚至创建了一个帮助类来定义它们。详情可见over here。
function asSettable()
this.get = function(key: string)
return this[key];
;
this.set = function(key: string, value)
this[key] = value;
return this;
;
function asEventable()
this.on = function(name: string, callback)
this._events = this._events || ;
this._events[name] = callback;
;
this.trigger = function(name: string)
this._events[name].call(this);
class Model
constructor (properties = )
;
static create(): IModel
return <any>new Model();
asSettable.call(Model.prototype);
asEventable.call(Model.prototype);
interface ISettable
get(key: string);
set(key: string, value);
interface IEvents
on(name: string, callback);
trigger(name: string);
interface IModel extends ISettable, IEvents
var x = Model.create();
x.set('foo', 'bar');
【讨论】:
正要发布这个。 TypeScript 确实应该被扩展以支持类的混合,因为目前很多 JS 库都使用它(例如 Backbone.js)。 从 ts1.4 开始,您可以使用“ISettable & IEvents”代替“IModel”【参考方案2】:虽然它仍然需要双重类型声明,但最简洁的方法是将 mixin 定义为模块:
module Mixin
export function on(test)
alert(test);
;
class TestMixin implements Mixin
on: (test) => void;
;
var mixed = _.extend(new TestMixin(), Mixin); // Or manually copy properties
mixed.on("hi");
使用接口的另一种方法是使用类来破解它(尽管由于多重继承,您需要为 mixins 创建一个公共接口):
var _:any;
var __mixes_in = _.extend; // Lookup underscore.js' extend-metod. Simply copies properties from a to b
class asSettable
getx(key:string) // renamed because of token-*** in asEventAndSettable
return this[key];
setx(key:string, value)
this[key] = value;
return this;
class asEventable
_events: any;
on(name:string, callback)
this._events = this._events || ;
this._events[name] = callback;
trigger(name:string)
this._events[name].call(this);
class asEventAndSettable
// Substitute these for real type definitions
on:any;
trigger:any;
getx: any;
setx: any;
class Model extends asEventAndSettable
/// ...
var m = __mixes_in(new Model(), asEventable, asSettable);
// m now has all methods mixed in.
正如我对 Steven 的回答所评论的,mixin 确实应该是 TypeScript 的功能。
【讨论】:
我什至会说第一个版本应该只是 TypeScript 实现 mixins 的方式——不会太难。 如果我正确理解 ts 语义,这两个选项的问题是它们失去了“功能性混合”的“功能性”部分。你只是用属性来扩展类,使这种风格的 mixin 很好的事情是你可以与 mixin 一起执行代码,这让你有机会保存一点点状态,或者你需要的任何东西去做。与其他语言相比,这种对函数的使用是 IMO 让 JS 变得有价值的原因(嗯......以及整个网络标准......),但除此之外,JS 只是一个弱替代品。 我相信你可以使用 var mixed = _.extend(TestMixin.prototype, Mixin);让生活更轻松【参考方案3】:一种解决方案是不使用 typescript 类系统,而只使用类型和接口的系统,以及关键字“new”。
//the function that create class
function Class(construct : Function, proto : Object, ...mixins : Function[]) : Function
//...
return function();
module Test
//the type of A
export interface IA
a(str1 : string) : void;
//the class A
//<new () => IA> === cast to an anonyme function constructor that create an object of type IA,
// the signature of the constructor is placed here, but refactoring should not work
//Class(<IA> === cast an anonyme object with the signature of IA (for refactoring, but the rename IDE method not work )
export var A = <new () => IA> Class(
//the constructor with the same signature that the cast just above
function() ,
<IA>
//!! the IDE does not check that the object implement all members of the interface, but create an error if an membre is not in the interface
a : function(str : string)
);
//the type of B
export interface IB
b() : void;
//the implementation of IB
export class B implements IB
b()
//the type of C
export interface IC extends IA, IB
c() : void;
mystring: string;
//the implementation of IC
export var C = <new (mystring : string) => IC> Class(
//public key word not work
function(mystring : string)
//problem with 'this', doesn't reference an object of type IC, why??
//but google compiler replace self by this !!
var self = (<IC> this);
self.mystring = mystring;
,
<IC>
c : function (),
//override a , and call the inherited method
a: function (str: string)
(<IA> A.prototype).a.call(null, 5);//problem with call and apply, signature of call and apply are static, but should be dynamic
//so, the 'Class' function must create an method for that
(<IA> this.$super(A)).a('');
,
//mixins
A, B
);
var c = new Test.C('');
c.a('');
c.b();
c.c();
c.d();//ok error !
【讨论】:
【参考方案4】:几年前在 Typescript 中构建了一种新方法,称为“mixin 类”。文档中没有很好地涵盖它,但他们确实有a well-commented example 来很好地描述这种模式。应用到您的情况,它可能看起来像:
type Constructor = new (...args: any[]) =>
function Settable<TBase extends Constructor>(Base: TBase)
return class extends Base
_props: Record<string, any> = ;
get(key: string)
return this._props[key];
set(key: string, value: any)
this._props[key] = value;
return this;
function Eventable<TBase extends Constructor>(Base: TBase)
return class extends Base
_events: Record<string, () => void> = ;
on(name: string, callback: () => void)
this._events[name] = callback;
trigger(name: string)
this._events[name].call(this);
class Model extends Settable(Eventable(Object))
constructor(properties = )
super();
这会让你得到你想要的打字,例如您可以致电(new Model()).set('boo', 'bar')
并提供完整的打字支持。没有虚拟声明。
【讨论】:
以上是关于TypeScript 中的 Mixin的主要内容,如果未能解决你的问题,请参考以下文章
Typescript 入门手册之函数类型在 TypeScript 中的应用
Typescript 入门手册之函数类型在 TypeScript 中的应用
Typescript入门手册之引用类型在TypeScript中的应用