[英]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.update
和someVar
之前,当您尝试使用它们时,您将获得未定义,并且编译器也不会捕获它。
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.