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