关于typescript:构造签名的接口如何工作?

How does interfaces with construct signatures work?

我在计算如何在接口中定义构造函数时遇到了一些困难。我可能完全误解了什么。但我已经找了很长一段时间的答案,我找不到与此相关的任何信息。

如何在typescript类中实现以下接口:

1
2
3
interface MyInterface {
    new ( ... ) : MyInterface;
}

AndersHejlsberg创建了一个界面,其中包含与此类似的内容(大约14分钟)。但对我来说,我不能在课堂上实现这一点。

我可能误解了什么,我没有得到什么?

编辑:

澄清。带有"new(…)"我的意思是"任何事"。我的问题是我连最基本的版本都得不到:

1
2
3
4
5
6
7
interface MyInterface {
    new () : MyInterface;
}

class test implements MyInterface {
    constructor () { }
}

这不是为我编译。我得到"class'test'声明接口'myinterface',但没有实现它:类型'myinterface'需要构造签名,但类型'test'在试图编译它时缺少一个。

编辑:

所以在研究了这一点之后,给出了更多的反馈。

1
2
3
4
5
6
7
interface MyInterface {
    new () : MyInterface;
}

class test implements MyInterface {
    constructor () => test { return this; }
}

不是有效的typescript,这无法解决问题。不能定义构造函数的返回类型。它将返回"测试"。以下签字:等级测试{施工方()}似乎是"new()=>test"(通过将鼠标悬停在在线编辑器中的"class"上,只粘贴该代码即可获得)。这就是我们想要的,我想是的。

有人能提供一个这样或类似的例子吗?在实际编译的地方?

编辑(再次…):

因此,我可能想出了一个想法,为什么可以在接口中定义它,但不可能在typescript类中实现它。以下是可行的:

1
2
3
4
5
6
7
8
9
10
11
12
var MyClass = (function () {
    function MyClass() { }
    return MyClass;
})();

interface MyInterface {
    new () : MyInterface;
}

var testFunction = (foo: MyInterface) : void =>  { }
var bar = new MyClass();
testFunction(bar);

那么,这仅仅是一个让你可以连接javascript的typescript特性吗?或者可以在不使用javascript实现类的情况下用typescript实现它吗?


接口中的构造签名不能在类中实现;它们只用于定义现有的JSAPI,这些API定义了一个"新的"可实现的函数。下面是一个涉及接口new签名的示例,它确实有效:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
interface ComesFromString {
    name: string;
}

interface StringConstructable {
    new(n: string): ComesFromString;
}

class MadeFromString implements ComesFromString {
    constructor (public name: string) {
        console.log('ctor invoked');
    }
}

function makeObj(n: StringConstructable) {
    return new n('hello!');
}

console.log(makeObj(MadeFromString).name);

这为您可以用以下方法调用makeObj创建了一个实际约束:

1
2
3
4
5
6
class Other implements ComesFromString {
    constructor (public name: string, count: number) {
    }
}

makeObj(Other); // Error! Other's constructor doesn't match StringConstructable


在我寻找完全相同的问题时,我去寻找排版团队是如何做到这一点的…

它们声明一个接口,然后声明一个名称与接口名称完全匹配的变量。这也是输入静态函数的方法。

来自lib.d.ts的示例:

1
2
3
4
5
6
7
8
9
10
11
interface Object {
    toString(): string;
    toLocaleString(): string;
    // ... rest ...
}
declare var Object: {
    new (value?: any): Object;
    (): any;
    (value: any): any;
    // ... rest ...
}

我试过了,很有魅力。


与构造签名的接口并不意味着由任何类来实现(乍一看,对于像我这样的C/Java背景的人来说,这可能看起来很奇怪,但给它一个机会)。这有点不同。

暂时把它想象成一个带有调用签名的接口(就像Java世界中的一个"函数接口")。它的目的是描述一个函数类型..类。所描述的签名应该由函数对象来满足……但不只是任何高级函数或方法。它应该是一个知道如何构造对象的函数,一个在使用new关键字时被调用的函数。

因此,带有构造签名的接口定义了构造函数的签名!类的构造函数应符合接口中定义的签名(将其视为构造函数实现接口)。就像建筑工人或工厂!

下面是一小段代码,试图演示最常见的用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
interface ClassicInterface { // old school interface like in C#/Java
    method1();
    ...
    methodN();
}

interface Builder { //knows how to construct an object
    // NOTE: pay attention to the return type
    new (myNumberParam: number, myStringParam: string): ClassicInterface
}

class MyImplementation implements ClassicInterface {
    // The constructor looks like the signature described in Builder
    constructor(num: number, s: string) { } // obviously returns an instance of ClassicInterface
    method1() {}
    ...
    methodN() {}
}

class MyOtherImplementation implements ClassicInterface {
    // The constructor looks like the signature described in Builder
    constructor(n: number, s: string) { } // obviously returns an instance of ClassicInterface
    method1() {}
    ...
    methodN() {}
}

// And here is the polymorphism of construction
function instantiateClassicInterface(ctor: Builder, myNumberParam: number, myStringParam: string): ClassicInterface {
    return new ctor(myNumberParam, myStringParam);
}

// And this is how we do it
let iWantTheFirstImpl = instantiateClassicInterface(MyImplementation, 3.14,"smile");
let iWantTheSecondImpl = instantiateClassicInterface(MyOtherImplementation, 42,"vafli");

从设计的角度来看,通常不需要在接口中指定构造函数需求。接口应该描述可以在对象上执行的操作。如果需要的话,应该允许实现接口的不同类需要不同的构造函数参数。

例如,如果我有一个接口:

1
2
3
4
interface ISimplePersistence {
    load(id: number) : string;
    save(id: number, data: string): void;
}

我可能有将数据存储为cookie(不需要构造函数参数)的实现,以及将数据存储在数据库(需要连接字符串作为构造函数参数)中的版本。

如果您仍然想在接口中定义构造函数,有一种肮脏的方法可以做到这一点,我曾经回答过这个问题:

带有构造签名的接口不是类型检查


为了达到预期的行为,你可以使用装饰,即使这可能不是他们应该使用的。

这个

1
2
3
4
5
6
7
8
9
10
11
12
interface MyInterface {
    new ();
}

function MyInterfaceDecorator(constructor: MyInterface) {
}


@MyInterfaceDecorator
class TestClass {
    constructor () { }
}

编译时没有问题。相反,下面的testclass定义

1
2
3
4
5
// error TS2345: Argument of type 'typeof TestClass' is not assignable to parameter of type 'MyInterface'.
@MyInterfaceDecorator
class TestClass {
    constructor (arg: string) { }
}

不会编译。