返回类构造函数的函数的类型声明

时间:2017-12-21 21:56:45

标签: javascript function typescript constructor declaration

我正在为具有全局结构的JavaScript游戏引擎编写TypeScript声明文件,而且我不太清楚如何处理返回类构造函数的函数。以下是代码的简化版本:

var createScript = function(name) {
  var script = function(args) {
    this.app = args.app;
    this.entity = args.entity;
  }
  script._name = name

  return script;
}

用户应使用自己的代码扩展script类构造函数,如下所示:

var MyScript = createScript('myScript');

MyScript.someVar = 6;

MyScript.prototype.update = function(dt) {
  // Game programming stuff
}

在我的声明文件中处理createScript函数和script类构造函数的正确方法是什么?

更新 我已经更新了我的代码示例,以表明用户也应该扩展"类"的静态方面。到目前为止给出的答案虽然很棒,但似乎并不允许这样做。

2 个答案:

答案 0 :(得分:3)

你可以将createScript的返回类型声明为某种通用脚本构造函数

interface AbstractScript { 
    app;
    entity;
}

interface ScriptConstructor<T extends AbstractScript> { 
    new(args: AbstractScript): T;
    readonly prototype: T;
}

declare function createScript(name: string): ScriptConstructor<any>;

然后

interface Foo extends AbstractScript { 
    foo(): void;
}

let Foo:ScriptConstructor<Foo> = createScript("Foo");
Foo.prototype.foo = function () { }

let d = new Foo({app: "foo", entity: "bar"});
d.foo();
d.entity
d.app

注意:出于方便原因,将构造函数和创建的类的接口命名为Foo
首先,TS可能有一个问题,但不,它很好地区分。

答案 1 :(得分:2)

在TypeScript中,表示类构造函数的一种可能方法是使用具有所谓constructor signature的接口。因为createScript应该返回用户创建的(或者更确切地说是用户修改的)类的实例,所以它必须是通用的。用户必须提供一个接口,将扩展类描述为createScript的通用参数:

export interface ScriptConstructorArgs {
    app: {};     // dummy empty type declarations here
    entity: {}; 
}
export interface Script {  // decsribes base class
    app: {};
    entity: {};
}

export interface ScriptConstructor<S extends Script> {
    _name: string;
    new(args: ScriptConstructorArgs): S;
}

// type declaration for createScript
declare var createScript: <S extends Script>(name: string) => ScriptConstructor<S>;

使用createScript时,用户必须描述扩展类,并通过分配原型单独提供其实现

interface MyScript extends Script {
    update(dt: {}): void;
}
var MyScript = createScript<MyScript>('myScript');

MyScript.prototype.update = function(dt) {
  // Game programming stuff
}

<强>更新

如果您希望用户也能够扩展构造函数类型(以自定义类的“静态”一侧),您可以通过一些额外的工作来完成。它涉及为自定义构造函数类型添加另一个泛型参数。用户还必须提供描述该类型的接口 - 在此示例中为MyScriptClass

export interface ScriptConstructorArgs {
    app: {};     // dummy empty type declarations here
    entity: {}; 
}
export interface Script {  // decsribes base class
    app: {};
    entity: {};
}

export interface ScriptConstructor<S extends Script> {
    _name: string;
    new(args: ScriptConstructorArgs): S;
}

// type declaration for createScript
declare var createScript: <Instance extends Script, 
                           Class extends ScriptConstructor<Instance>
                        >(name: string) => Class;

interface MyScript extends Script {
    update(dt: {}): void;
}
interface MyScriptClass extends ScriptConstructor<MyScript> {
    someVar: number;
}
var MyScript = createScript<MyScript, MyScriptClass>('myScript');

MyScript.prototype.update = function(dt) {
  // Game programming stuff
}

MyScript.someVar = 6;

请注意,在此解决方案中,编译器并未真正检查它 提供的实现符合声明的接口 - 您可以为prototype.update分配任何内容,编译器不会抱怨。此外,在分配prototype.updatesomeVar之前,当您尝试使用它们时会得到未定义,编译器也不会捕获它。

另一个更新

prototype.udpate的分配未进行类型检查的原因是,MyScript的静态部分被推断为Function,并且Function.prototypebuilt-in libraryany,禁止进行类型检查。有一个简单的解决方法:只需声明您自己的,更具体的prototype

export interface ScriptConstructor<S extends Script> {
    _name: string;
    new(args: ScriptConstructorArgs): S;
    prototype: S;
}
然后它开始捕捉这样的错误:

MyScript.prototype.udpate = function(dt) {
  // Game programming stuff
}
// Property 'udpate' does not exist on type 'MyScript'. Did you mean 'update'?

此外,dt参数类型也是从MyScript接口推​​断出来的。

实际上我没有太多经验做这样的事情,所以我不知道声明你自己的原型是否会在将来导致其他代码出现问题。

此外,如果prototype.update的作业完全丢失,它也不会抱怨 - 它仍会相信update,因为它是以createScript类型声明的。这是从javascript实现中“组装”一个类以及一些外部类型声明所付出的代价 - 创建类的“普通”,all-typescript方法只是使用类似class MyScript extends ScriptBase implements SomeInterface的东西,然后它保证是typechecked。