Tipografia datilografada, genéricos e classes abstratas
Eu experimento um comportamento que me parece estranho.
Vamos considerar a seguinte amostra (testá-lo no playground Typcript):
abstract class FooAbstract {
abstract bar() {}
}
class Foo extends FooAbstract {
bar() {
return { bar: 'bar' };
}
}
class FooMaker<FOO extends FooAbstract> {
constructor(public foo: FOO) {}
bar() {
return this.foo.bar();
}
baz = () => {
return this.foo.bar();
}
}
let foo = new Foo();
let result = foo.bar();
let foomaker = new FooMaker(new Foo);
let foo2 = foomaker.foo; // Type "Foo", OK
let result1 = foomaker.foo.bar(); // Type "{bar: string}", OK
let result2 = foomaker.bar(); // Type "{}", ???
let result3 = foomaker.baz(); // I've seen comments about using a lambda... Not better
result2
eresult3
são digitados como o resumobar
função ({}
) Parece quethis
não é resolvido como a classe concretaFoo
mas como a classe abstrataFooAbstract
. Considerando que o tipo defoo2
mostra que a classefoo
A propriedade foi resolvida corretamente.
O que está acontecendo? Faço algo da maneira errada?
atualizarComo uma reflexão tardia, esse caso pode ser reformulado dessa maneira (Teste-o no playground Typcript):
class Foo {
bar() {
return { bar: 'bar' };
}
getThis(): this {
return this
}
}
class Wrapper {
bar<FOO extends { bar(): {} }>(foo:FOO) {
return foo.bar();
}
}
let wrapper = new Wrapper();
let result = (new Foo()).bar();
let result2 = wrapper.bar(new Foo());
result
tem o tipo{bar:string}
result2
tem o tipo{}
(a partir da interface).wrapper.bar
tem o tipoWrapper.bar<Foo>(foo: Foo): {}
Com esta amostra, fica mais claro que, mesmo sabendo queFOO
é digitado comoFoo
, Texto datilografado usaFOO
definição e não seu tipo explícito comobar
tipo de retorno.
Ok, enquanto lutava com digitações, acho que subi de nível. O conceito é que tipificações implícitas emTexto datilografado não siga nenhum modelo de herança, mesmo quando um tipo é deduzido. Bem, eu ainda me perguntoporque ouisso vai mudar, mas vou ter que lidar com "é assim". Portanto, neste caso, o tipo deve ser explícito.
Encontrei uma maneira mais simples de escrever seu exemplo (tente no playground Typcript):
abstract class FooAbstract {
abstract bar(): {}
}
class Foo extends FooAbstract {
bar() {
return { bar: 'bar' };
}
}
class FooMaker<FOO extends FooAbstract, BAR> {
constructor(public foo: FOO & { bar: () => BAR } ) {
}
bar():BAR {
return this.foo.bar() as BAR;
}
}
let foomaker = new FooMaker(new Foo());
let result = foomaker.bar();
result
obtém o tipo{bar:string}
e não há necessidade de colocar genéricos em todos os lugares. As coisas noFooMaker.constructor
O tipo de parâmetro pode ficar mais limpo consultando uma interface com um genérico.