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

问题描述 投票:3回答:2

我正在为具有全局结构的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类构造函数的正确方法是什么?

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

javascript function typescript constructor declaration
2个回答
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
}

UPDATE

如果您希望用户也能够扩展构造函数类型(以自定义类的“静态”方面),您可以通过一些额外的工作来完成。它涉及为自定义构造函数类型添加另一个泛型参数。用户还必须提供描述该类型的接口 - 在此示例中为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 library中被声明为any,它抑制了类型检查。有一个简单的解决方法:只需声明自己的,更具体的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实现中“组装”一个类以及一些外部类型声明所付出的代价 - 创建类的“正常”,所有打字方法只是使用像class MyScript extends ScriptBase implements SomeInterface这样的东西,然后它保证被类型化。


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可能有一个问题,但不是,它很好地区分。

© www.soinside.com 2019 - 2024. All rights reserved.