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

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

最佳答案

在 TypeScript 中,表示类构造函数的一种可能方法是使用带有所谓 constructor signature 的接口(interface)。因为 createScript 应该返回用户创建(或更准确地说,用户修改)类的实例,所以它必须是通用的。用户必须提供一个接口(interface),将扩展类描述为 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 时,用户必须描述扩展类并通过分配给原型(prototype)单独提供其实现
interface MyScript extends Script {
    update(dt: {}): void;
}
var MyScript = createScript<MyScript>('myScript');

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

更新

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

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

另一个更新

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 接口(interface)推断出来的。

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

此外,如果对 prototype.update 的赋值完全丢失,它也不会提示——它仍然会相信 update 存在,因为它是在 createScript 类型中声明的。这就是从 javascript 实现中“组装”一个类以及一些外部类型声明的代价——创建类的“正常”、全 typescript 方式只是使用类似 class MyScript extends ScriptBase implements SomeInterface 的东西,然后保证会被类型检查。

关于javascript - 返回类构造函数的函数的类型声明,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/47933255/

10-11 22:45
查看更多