Pesquisa de site

Confusão de tipagem 'this' no Typescript


Estou me referindo a "this-typing" mencionada aqui, e aqui.

É meu entendimento que usar this como um tipo se refere à classe atual, ou ao que resta do ponto (permitindo assim que métodos herdados se refiram à sua própria classe em vez da classe do pai).

Então alguém pode explicar por que isso não funciona:

class Test {
    children: Array<this>;

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

(Meu objetivo é fazer isso com uma classe herdada, mas não funciona com uma classe base. Já que this é do tipo Test, por que children não pode ser um array de Test?

Resposta #1

Não, ao usar this como um tipo, você está se referindo à instância e não à classe.
Isso é chamado de tipos polimórficos de this e deve ser usado assim:

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

(código no playground)

Se Builder2D.x() e Builder2D.y() tivessem retornado Builder2D:

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

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

Então isso falharia:

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

Com :

A propriedade 'z' não existe no tipo 'Builder2D'

No seu cenário, este não é o caso, você não quer retornar this.
Pelo que eu sei, não existe um tipo para a classe de this, mas você pode fazer:

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

(código no playground)

Editar

Parece que há um problema com isso: "this" polimórfico para membros estáticos.

Resposta #2

Vamos definir a classe derivada:

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

Como você já apontou - this como tipo se refere à classe atual, então o membro children de TestDerived é do tipo TestDerived[]. Então podemos fazer algo assim:

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

Se o TypeScript nos permitir preencher este array (no construtor do super) com instâncias de Test, perderemos a segurança de tipo (someMethod não está definido em Test).

Artigos relacionados