ChadF
ChadF

Reputation: 1758

Typescript "this-typing" confusion

I am referring to "this-typing" referenced here, and here.

It is my understanding that using this as a type refers to the current class, or whatever is left of the dot (thereby allowing inherited methods to refer to their own class instead of their parent's class).

So can someone explain why this doesn't work:

class Test {
    children: Array<this>;

    constructor() {
        this.children = [new Test()];
    }
}

(My goal is to do that with an inherited class, but it doesn't work with a base class. Since this is of type Test why can't children be an array of Test?

Upvotes: 3

Views: 191

Answers (2)

Nitzan Tomer
Nitzan Tomer

Reputation: 164129

No, when using this as a type you are referring to the instance and not the class.
It's called Polymorphic this types and is meant to be used like this:

class Point {}

class Point2D extends Point {
    constructor(public x: number, public y: number) {
        super();
    }
}

class Point3D extends Point2D {
    constructor(x: number, y: number, public z: number) {
        super(x, y);
    }
}

class Builder2D {
    protected _x: number;
    protected _y: number;

    x(x: number): this {
        this._x = x;
        return this;
    }

    y(y: number): this {
        this._y = y;
        return this;
    }

    build(): Point {
        return new Point2D(this._x, this._y);
    }
}

class Builder3D extends Builder2D {
    private _z: number;

    z(z: number): this {
        this._z = z;
        return this;
    }

    build(): Point3D {
        return new Point3D(this._x, this._y, this._z);
    }
}

let p1 = new Builder3D().x(0).y(0).z(0).build();

(code in playground)

If Builder2D.x() and Builder2D.y() would have returned Builder2D:

x(x: number): Builder2D {
    this._x = x;
    return this;
}

y(y: number): Builder2D {
    this._y = y;
    return this;
}

Then this would fail:

let p1 = new Builder3D().x(0).y(0).z(0).build();

With:

Property 'z' does not exist on type 'Builder2D'

In your scenario this isn't the case, you don't want to return this.
As far as I'm aware there's no type for the class of this, but you can do:

class Test {
    public children: Array<Test>;

    constructor() {
        this.children = [new Test()];
    }
}

interface OtherTest {
    children: Array<OtherTest>;
}
class OtherTest extends Test {
    constructor() {
        super();
        this.children.push(new Test(), new OtherTest());
    }
}

let t1 = new Test();
let c1 = t1.children[0]; // typeof c1 is Test

let t2 = new OtherTest();
let c2 = t2.children[0]; // typeof c2 is OtherTest

(code in playground)


Edit

Seems like there's an issue for that: Polymorphic "this" for static members.

Upvotes: 3

Aleksey L.
Aleksey L.

Reputation: 37918

Let's define derived class:

class TestDerived extends Test {
    someMethod():void { }
}

As you've already pointed out - this as type refers to current class, so children member of TestDerived is of type TestDerived[]. So we can do something like this:

let b = new TestDerived ();
b.children[0].someMethod();

If typescript will allow us to populate this array (in constructor of super) with instances of Test, we will loose type safety (someMethod is not defined in Test).

Upvotes: 1

Related Questions