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

我如何在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”上并粘贴该代码获得)。这就是我们想要的,也是我想要的。

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

编辑(…):

所以我可能想到了一个想法,为什么可以在接口中定义这个,但不可能在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实现类?


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

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

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

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

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

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


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

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 ...
}

我试过了,效果很不错。


为了实现预期的行为,您可以使用decorator,即使这可能不是它们应该被用于的目的。

This

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");

为了扩展Nils的答案,你也可以用相同的技术创建一个通用的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) { ... }
  }
}

来自官方文件

This is because when a class implements an interface, only the instance side of the class is checked. Since the constructor sits in the static side, it is not included in this check. Instead, you would need to work with the static side of the class directly. In this example, we define two interfaces, ClockConstructor for the constructor and ClockInterface for the instance methods. Then, for convenience, we define a constructor function createClock that creates instances of the type that is passed to it:

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]