Blog-Archiv

Sonntag, 10. Juni 2018

TypeScript Constructor in Interface

In TypeScript you can describe a constructor in an interface, using the new keyword:

interface Point
{
    new(x: number, y: number);

    x: number;
    y: number;
}

(Marked red because it is not possible that way, although syntactically correct!)

The TypeScript language specification says about this

An object type containing one or more construct signatures is said to be a constructor type.

and

Constructor types may be written using constructor type literals or by including construct signatures in object type literals.

Lots of new terms, further explanations missing. Constructors can not be described in Java interfaces, such a TypeScript feature would be something completely new.

Considering this, a question comes to my mind: What if you receive a JSON object from an AJAX call, cast it to an interface that holds a constructor signature, could you then construct a new object of that type, maybe even without having loaded the definition of that class? I can't believe this. But when no, what sense does it make to give a constructor signature in an interface?

On stackoverflow I found this:

Construct signatures in interfaces are not implementable in classes; they're only for defining existing JS APIs that define a 'new'-able function.

So it looks like you can't use them in interfaces to describe the constructor of the implementing class. This syntax exists just to describe JavaScript built-in types like Date or RegExp.

New Terms

The construct signature

new <RETURNTYPE>(...constructorParameters: any): RETURNTYPE

would look like the following in an example for the type Point

new (x: number, y: number): Point

The constructor type literal

new <RETURNTYPE>(...constructorParameters: any) => RETURNTYPE

is equivalent to the object type literal

{
    new <RETURNTYPE>(...constructorParameters: any): RETURNTYPE;
}

Thus following example describing the constructor of type Point

new (x: number, y: number) => Point

is equivalent to

{
    new (x: number, y: number): Point;
}

Test Code

Compiling the following TypeScript code

ConstructableInterface.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
interface Point
{
    new(x: number, y: number);
    
    x: number;
    y: number;
}

class PointImpl implements Point
{
    readonly x: number;
    readonly y: number;
    
    constructor(x: number, y: number) {
        this.x = x;
        this.y = y;
    }
}

const point: Point = new PointImpl(1, 2);

you would receive the compile error

ConstructableInterface.ts(9,7): error TS2420: Class 'PointImpl' incorrectly implements interface 'Point'.
  Type 'PointImpl' provides no match for the signature 'new (x: number, y: number): any'.
ConstructableInterface.ts(20,7): error TS2322: Type 'PointImpl' is not assignable to type 'Point'.
  Type 'PointImpl' provides no match for the signature 'new (x: number, y: number): any'.

Mind that the compiler reports any as return-type of the construct signature, so it not even infers that the result would be the type itself! The TS Handbook justification is:

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.

To use a construct signature in an interface you must implement it like the following:

FactoryFunction.ts
 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
interface Point
{
    x: number;
    y: number;
}

interface PointConstructor
{
    new(x: number, y: number): Point;
}

class PointImpl implements Point
{
    readonly x: number;
    readonly y: number;
    
    constructor(x: number, y: number) {
        this.x = x;
        this.y = y;
    }
}

function newPoint(pointConstructor: PointConstructor, x: number, y: number): Point {
    return new pointConstructor(x, y);
}

const point: Point = newPoint(PointImpl, 1, 2);

You must separate the constructor description from the type description (interface) and put it into its own interface. The class PointImpl must not to implement PointConstructor, this is implicitly checked by the compiler when calling newPoint(). In case you implement it, you will see this compile error:

FactoryFunction.ts(12,7): error TS2420: Class 'PointImpl' incorrectly implements interface 'PointConstructor'.
  Type 'PointImpl' provides no match for the signature 'new (x: number, y: number): Point'.

The interface PointConstructor seems to be the constructor type. Finally, in the line const point: Point = newPoint(PointImpl, 1, 2), the constructor turns out to be the name of the implementing class, a little bit unexpected.

Conclusion

Currently I can not find any practical sense in this, because instead of passing the type to the factory I could also construct an object of that type directly via new. But maybe I haven't had to integrate JS implementations into TS code yet.




Keine Kommentare: