ChatGPT解决这个技术问题 Extra ChatGPT

How does interfaces with construct signatures work?

I am having some trouble working out how defining constructors in interfaces work. I might be totally misunderstanding something. But I have searched for answers for a good while and I can not find anything related to this.

How do I implement the following interface in a TypeScript class:

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

Anders Hejlsberg creates an interface containing something similar to this in this video (at around 14 minutes). But for the life of me I can not implement this in a class.

I am probably misunderstanding something, what am I not getting?

EDIT:

To clarify. With "new ( ... )" I meant "anything". My problem is that I can not get even the most basic version of this working:

interface MyInterface {
    new () : MyInterface;
}

class test implements MyInterface {
    constructor () { }
}

This is not compiling for me I get "Class 'test' declares interface 'MyInterface' but does not implement it: Type 'MyInterface' requires a construct signature, but Type 'test' lacks one" when trying to compile it.

EDIT:

So after researching this a bit more given the feedback.

interface MyInterface {
    new () : MyInterface;
}

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

Is not valid TypeScript and this does not solve the problem. You can not define the return type of the constructor. It will return "test". The signature of the following: class test { constructor () { } } Seems to be "new () => test" (obtained by hovering over "class" in the online editor with just that code pasted in). And this is what we would want and what i thought it would be.

Can anyone provide an example of this or something similar where it is actually compiling?

EDIT (again...):

So I might have come up with an idea as to why it is possible to define this in an interface but not possible to implement in a TypeScript class.The following works:

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

interface MyInterface {
    new () : MyInterface;
}

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

So is this only a feature of TypeScript that lets you interface javascript? Or is it possible to implement it in TypeScript without having to implement the class using javascript?

Sorry for the earlier incorrect feedback. I'm still looking, and honestly my best guess is that ctor constructs are not supposed to be able to return a type, and the ability to specify one in the interface might be a bug.
@asawyer Yes it seems weird. It is even stranger though if Anders Hejlsberg would use something in the official video that is not usable.
I posted a question on the codeplex site. typescript.codeplex.com/discussions/403413
It looks like it's the same with call signatures. Same as you cannot create class that implements a construct signature, you cannot create one that implements a call signature either.

R
Ryan Cavanaugh

Construct signatures in interfaces are not implementable in classes; they're only for defining existing JS APIs that define a 'new'-able function. Here's an example involving interfaces new signatures that does work:

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

This creates an actual constraint for what you can invoke makeObj with:

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

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

As I suspected in my last edit of the question then. and with that I think we can call this question answered.
More formally, a class implementing an interface is a contract on what an instance of the class has. Since an instance of a class won't contain a construct signature, it cannot satisfy the interface.
It would be really nice to have this highlighted in TypeScript's handbook (documentation).
@RyanCavanaugh, say you have an array of interfaces const objs: ComesFromString[] = [MadeFromString, AnotherOne, MoreString]; Now, how would I go about creating instances from those? say in a loop: _.each(objs, (x) => makeObj(x)? This will throw an error since x is of type ComesFromString and doesn't have a constructor.
ComesFromString is not the correct type annotation for objs as it implies each objs element is an instance of that type, not a constructor for it. You want objs: StringConstructable[]
N
Nils

On my search for the exact same question I went looking how the TypeScript-Team did that...

They are declaring an interface and afterwards a variable with a name matching exactly the interface-name. This is also the way to type static functions.

Example from lib.d.ts:

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

I tried that and it works like charm.


I'm using same pattern, and to make it less ugly, just write " } declare { " in a single line, so it looks like a single two-step construction.
This should be the accepted answer. Changing the library in order to satisfy an interface requirement is not reasonable.
I hit upon the same idea for creating instances of the same type. public reparse(statement: string): this { type t = new (statement: string) => this; let t = this.constructor as t; return new t(statement); } Identifiers are associated with three things: values, types, and namespaces. Things like classes that associate both with a name can get out of sync
How do you then provide an implementation for the new function?
e
egelev

Well an interface with a construct signature is not meant to be implemented by any class(at first glance this might look weird for guys with C#/Java background like me but give it a chance). It is slightly different.

For a moment think of it as a interface with a call signature(like a @FunctionalInterface in Java world). Its purpose is to describe a function type..kind of. The described signature is supposed to be satisfied by a function object...but not just any high level function or a method. It should be a function which knows how to construct an object, a function that gets called when new keyword is used.

So an interface with a construct signature defines the signature of a constructor ! The constructor of your class that should comply with the signature defined in the interface(think of it as the constructor implements the interface). It is like a factory !

Here is a short snippet of code that tries to demonstrate the most common usage:

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

C
Community

From the official documentation

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

C
Community

From a design perspective, it isn't usual to specify the constructor requirements in an interface. The interface should describe the operations you can perform on an object. Different classes that implement the interface should be allowed to require different constructor parameters if they need to.

For example, if I had an interface:

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

I might have implementations for storing the data as a cookie, which needs no constructor parameters, and a version that stores the data in a database, which needs a connection string as a constructor parameter.

If you are still want to define constructors in an interface, there is a dirty way to do this, which I used to answer this question:

Interfaces with construct signatures not type checking


I agree with the idea of generally not including constructors in an interface. But still, why is it possible if it is not possible to implement it in a TypescriptClass? (without using your hack that is)
I can't find anything in the language specification that suggests that it is possible to specify constructors on an interface. Section 4.11 covers the new keyword and only says it is valid for creating instances, i.e. var x = new MyClass();.
@SteveFenton I can't find anything in the spec either. Edit - not three seconds after I post this - Section 3.5.3.2 Construct Signatures.
That makes it even more interesting that Anders Hejlsberg uses this in the official video on the typescriptlang.org sitem at around 14 minutes (mentioned in my original post). He does not however implement it in a class, hence my question.
I've checked out the video - the interface Anders creates is not implementable in a TypeScript class.
M
MarvinDV

To achieve the intended behaviour you could use Decorators, even though that is probably not what they are supposed to be used for.

This

interface MyInterface {
    new ();
}

function MyInterfaceDecorator(constructor: MyInterface) {
}


@MyInterfaceDecorator
class TestClass {
    constructor () { }
}

compiles without a problem. In contrast, the following definition for TestClass

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

will not compile.


D
DomQ

To expand on Nils' answer, you can also make a generic new-able function with the same technique:

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

Я
Як Цидрак

you can use type instead

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

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

Your answer could be improved by adding more information on what the code does and how it helps the OP.