具有构造签名的接口如何工作?

我在解决如何在接口中定义构造函数工作方面遇到了一些麻烦。我可能完全误解了一些事情。但我已经寻找了很长一段时间的答案,我找不到任何与此相关的东西。

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

interface MyInterface {
new ( ... ) : MyInterface;
}

Anders Hejlsberg在视频中创建了一个包含类似内容的界面(大约14分钟)。但是无论如何我都不能在一个类中实现这个。

我可能误解了什么,我没明白什么?

编辑:

澄清。用“new(…)”我的意思是“任何事”。我的问题是,我甚至不能得到这个工作的最基本的版本:

interface MyInterface {
new () : MyInterface;
}


class test implements MyInterface {
constructor () { }
}

这不是编译对我来说,我得到“类'test'声明接口'MyInterface',但没有实现它:类型'MyInterface'需要一个构造签名,但类型'test'缺乏一个”,当试图编译它。

编辑:

所以在研究了更多的反馈之后。

interface MyInterface {
new () : MyInterface;
}


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

不是有效的TypeScript,这不能解决问题。不能定义构造函数的返回类型。它将返回"test"。以下签名: 类测试{ 构造函数(){} } 似乎是“new () => test”(通过将鼠标悬停在在线编辑器中的“class”上并粘贴该代码获得)。这就是我们想要的,也是我想要的。< / p >

谁能提供一个这样的例子或类似的东西,它实际上是编译的?

编辑(…):

所以我可能想到了一个想法,为什么可以在接口中定义这个,但不可能在TypeScript类中实现。以下工作:

var MyClass = (function () {
function MyClass() { }
return MyClass;
})();


interface MyInterface {
new () : MyInterface;
}


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

那么,这仅仅是TypeScript的一个功能,让你可以接口javascript吗?或者有可能在TypeScript中实现它,而不必使用javascript实现类?

133135 次浏览

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

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

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

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

如果你仍然想在接口中定义构造函数,有一种很脏的方法可以做到这一点,我用它来回答这个问题:

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

接口中的构造签名不能在类中实现;它们只用于定义现有的定义“新”功能的JS api。下面是一个涉及接口new签名的示例,该签名有效:

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创建了一个实际的约束:

class Other implements ComesFromString {
constructor (public name: string, count: number) {
}
}


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

在我搜索完全相同的问题时,我去看TypeScript-Team是如何做到的…

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

来自lib.d.ts的例子:

interface Object {
toString(): string;
toLocaleString(): string;
// ... rest ...
}
declare var Object: {
new (value?: any): Object;
(): any;
(value: any): any;
// ... rest ...
}

我试过了,效果很不错。

为了实现预期的行为,你可以使用修饰符,即使这可能不是他们应该被用于。

interface MyInterface {
new ();
}


function MyInterfaceDecorator(constructor: MyInterface) {
}




@MyInterfaceDecorator
class TestClass {
constructor () { }
}

编译没有问题。相反,下面是TestClass的定义

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

不会编译。

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

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

因此,具有构造签名的接口定义了构造函数的签名!类的构造函数应该符合接口中定义的签名(将其视为实现接口的构造函数)。它就像一个工厂!

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

interface ClassicInterface { // old school interface like in C#/Java
method1();
...
methodN();
}


interface Factory { //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 Factory
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 Factory
constructor(n: number, s: string) { } // obviously returns an instance of ClassicInterface
method1() {}
...
methodN() {}
}


// And here is the polymorphism of construction
function instantiateClassicInterface(ctor: Factory, 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");

为了扩展尼尔斯' 回答,你也可以使用相同的技巧创建一个通用的new-able函数:

interface MyArrayConstructor {
<T>(...elements: Array<T>): MyArrayInstance<T>
new <T> (...elements: Array<T>): MyArrayInstance<T>
}


// “Typecast” not the function itself, but another symbol,
// so that the body of myArray will also benefit from
// type-checking:
export const MyArray = myArray as MyArrayConstructor


interface MyArrayInstance<T> {
push(...args: Array<T>): number
slice(from?: number, to?:number): Array<T>
}


function myArray(...elements: Array<T>): MyArrayInstance<T> {
return {
push(...args) { ... },
slice(from?: number, to?: number) { ... }
}
}

来自官方< >强文档< / >强

这是因为当类实现接口时,只检查类的实例端。由于构造函数位于静态端,因此不包含在此检查中。

相反,您需要直接使用类的静态部分。在这个例子中,我们定义了两个接口,构造函数的ClockConstructor和实例方法的ClockInterface。然后,为了方便起见,我们定义了一个构造函数createClock,它创建传递给它的类型的实例:

interface ClockConstructor {
new (hour: number, minute: number): ClockInterface;
}


interface ClockInterface {
tick(): void;
}


function createClock(
ctor: ClockConstructor,
hour: number,
minute: number
): ClockInterface {
return new ctor(hour, minute);
}


class DigitalClock implements ClockInterface {
constructor(h: number, m: number) {}
tick() {
console.log("beep beep");
}
}


class AnalogClock implements ClockInterface {
constructor(h: number, m: number) {}
tick() {
console.log("tick tock");
}
}


let digital = createClock(DigitalClock, 12, 17);
let analog = createClock(AnalogClock, 7, 32);

你可以用type代替

class SomeClass {
constructor(param1: string, param2: number) {
}
}


type Params = new (param1: string, param2: number) => SomeClass
type ConstructParams = ConstructorParameters<Params> // [string, number]