简体   繁体   English

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

[英]Type declaration for function that returns a class constructor

I'm writing a TypeScript declaration file for a JavaScript game engine with a global structure and I don't quite know how to deal with this function that returns a class constructor. 我正在为具有全局结构的JavaScript游戏引擎编写TypeScript声明文件,我不知道如何处理这个返回类构造函数的函数。 Here is a simplified version of the code: 这是代码的简化版本:

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

  return script;
}

Users are meant to extend the script class constructor with their own code like this: 用户应该使用自己的代码扩展script类构造函数,如下所示:

var MyScript = createScript('myScript');

MyScript.someVar = 6;

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

What is the proper way to deal with the createScript function and script class constructor in my declaration file? 在我的声明文件中处理createScript函数和script类构造函数的正确方法是什么?

UPDATE I have updated my code example to show that users are also expected to extend the static side of the "class". 更新我已经更新了我的代码示例,以表明用户也应该扩展“类”的静态方面。 The answers given so far, though awesome, don't seem to allow for that. 到目前为止给出的答案虽然很棒,但似乎并不允许这样做。

you can declare the returntype of createScript as some kind of generic script constructor 您可以将createScript的返回类型声明为某种通用脚本构造函数

interface AbstractScript { 
    app;
    entity;
}

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

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

and then 然后

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

Note: named the constructor and the interface of the created class both Foo for conveniance reasons. 注意:出于方便原因,将构造函数和创建的类的接口命名为Foo
Thaught first, TS might have an issue with that, but no, it differentiates nicely. 首先,TS可能有一个问题,但不是,它很好地区分。

In TypeScript, one possible way to represent a class constructor is by using an interface with so-called constructor signature . 在TypeScript中,表示类构造函数的一种可能方法是使用具有所谓构造函数签名的接口。 Because createScript is supposed to return an instance of user-created (or, more precisely, user-modified) class, it has to be generic. 因为createScript应该返回用户创建的(或者更确切地说是用户修改的)类的实例,所以它必须是通用的。 The user will have to provide an interface that describes extended class as generic paratemeter for 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>;

When using createScript , users must describe extended class and separately provide its implementation by assigning to the prototype 使用createScript ,用户必须描述扩展类,并通过分配原型单独提供其实现

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

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

UPDATE UPDATE

If you want users to be able to extend constructor type too (to customize "static" side of the class), you can do that with some extra work. 如果您希望用户也能够扩展构造函数类型(以自定义类的“静态”方面),您可以通过一些额外的工作来完成。 It involves adding another generic parameter for customized constructor type. 它涉及为自定义构造函数类型添加另一个泛型参数。 Users also have to provide an interface describing that type - MyScriptClass in this example: 用户还必须提供描述该类型的接口 - 在此示例中为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;

Just be aware that in this solution the compiler does not really check that provided implementation conforms to the declared interface - you can assign anything to prototype.update and compiler will not complain. 请注意,在此解决方案中,编译器并未真正检查提供的实现是否符合声明的接口 - 您可以为prototype.update分配任何内容,编译器不会抱怨。 Also, until prototype.update and someVar are assigned, you get undefined when you try to use them, and compiler will not catch that too. 此外,在分配prototype.updatesomeVar之前,当您尝试使用它们时,您将获得未定义,并且编译器也不会捕获它。

ANOTHER UPDATE 另一个更新

The reason why the assignment to prototype.udpate is not typechecked is that somehow the static part of MyScript is inferred to be a Function , and Function.prototype is declared in the built-in library as any , which suppresses type checking. prototype.udpate的赋值没有进行类型检查的原因是, MyScript的静态部分被推断为Function ,而Function.prototype内置库中被声明为any ,这会抑制类型检查。 There is an easy fix: just declare your own, more specific prototype : 有一个简单的解决方法:只需声明自己的,更具体的prototype

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

then it starts catching errors like this: 然后它开始捕获这样的错误:

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

Also, dt parameter type is also inferred from the MyScript interface. 此外,还从MyScript界面推断出dt参数类型。

Actually I don't have much experience doing stuff like this, so I don't know if declaring your own prototype could cause some problems with other code in the future. 实际上我没有太多经验做这样的事情,所以我不知道声明你自己的原型是否会在将来导致其他代码出现问题。

Also, it will not complain if the assignment to prototype.update is missing altogether - it will still believe update is there because it was declared in createScript type. 此外,如果对prototype.update的赋值完全丢失,它也不会抱怨 - 它仍然会认为update是因为它是以createScript类型声明的。 That's the price to pay for "assembling" a class from javascript implementation together with some external type declarations - the "normal", all-typescript way to create a class is just use something like class MyScript extends ScriptBase implements SomeInterface , then it's guaranteed to be typechecked. 这是从javascript实现中“组装”一个类以及一些外部类型声明所付出的代价 - 创建类的“普通”,all-typescript方法只是使用class MyScript extends ScriptBase implements SomeInterface ,然后它保证被挑剔了。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM