打字稿类型、泛型和抽象类 [英] Typescript typings, generics and abstract classes
问题描述
我尝试了一种我觉得很奇怪的行为.
I experiment a behavior that seems strange to me.
让我们考虑以下示例(在 Typescript playground 测试):
Let's consider the following sample (test it in Typescript playground):
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
和 result3
的类型类似于抽象的 bar
函数 ({}
).似乎this
没有被解析为具体类Foo
,而是作为抽象类FooAbstract
.而 foo2
的类型表明类 foo
属性被正确解析.
result2
and result3
are typed like the abstract bar
function ({}
). It seems that this
isn't resolved as the concrete class Foo
but as the abstract class FooAbstract
. Whereas the type of foo2
shows that the class foo
property is resolved correctly.
这是怎么回事?我做错了什么吗?
What is going on? Do I do something the wrong way?
事后想来,这个案例可以这样重新表述(在Typescript playground中测试):
As an afterthought, this case can be reformulated like that (Test it in Typescript playground):
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
的类型为 {bar:string}
result2
具有 {}
类型(来自界面).wrapper.bar
的类型为 Wrapper.bar
result
has the type {bar:string}
result2
has the type {}
(from the interface).
wrapper.bar
has the type Wrapper.bar<Foo>(foo: Foo): {}
通过这个示例,更清楚的是,即使知道 FOO
的类型为 Foo
,Typescript 使用 FOO
定义而不是它的显式类型作为 bar
返回类型.
With this sample, it's clearer that, even when knowing that FOO
is typed as Foo
, Typescript uses FOO
definition and not its explicit type as bar
return type.
好的,在与打字斗争的同时,我想我已经升级了.这个概念确实是 Typescript 中的隐式类型不遵循任何继承模型,即使在推导类型时也是如此.好吧,我仍然想知道为什么或它会改变,但我必须应付就是这样".所以在这种情况下,类型必须是显式的.
Ok, while fighting with typings, I think I leveled up. The concept is indeed that implicit typings in Typescript don't follow any inheritance model even when a type is deduced. Well, I still wonder why or is it going to change, but I'll have to cope with "it's like that". So in this case the type has to be explicit.
我找到了一种更简单的方法来编写他的示例(在 Typescript 游乐场尝试):
I found a simpler way to write his example (try it in Typescript playground):
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
获取类型 {bar:string}
并且不需要在任何地方放置泛型.FooMaker.constructor
参数类型中的内容可以通过引用具有泛型的接口变得更清晰.
result
gets the type {bar:string}
and no need to put generics everywhere. The stuff in the FooMaker.constructor
parameter type could get cleaner by referring an interface with a generic.
推荐答案
这里有一个清晰的答案以及传递方法返回类型所需的示例.
Here is a clean answer and example of what is needed to pass method return types.
嵌入另一个对象的对象使用其内部声明的类型(在本例中为抽象类型)来确定其函数的返回类型.即使该对象类型已知(或明确声明).
An object that embeds another object uses its internally declared type (in this case the abstract type) to determine its functions return type. Even when that object type is known (or explicitly declared).
换句话说,Typescript 类型推断不会查看对象方法内部来推断类型.
In other words, Typescript type inference doesn't look inside the object methods to deduce a type.
我发现处理这种情况的唯一解决方案是将泛型与方法/函数的返回类型相关联,并将对象结构与它们相匹配.
The only solution I found to handle that case is to associate generics to the methods/functions return types, and to match the object structure with them.
基于我的问题更新 2(在Typescript playground中测试):
interface TestInterface<ASNUM, ASSTRING, ASOBJECT> {
asNum: () => ASNUM
asString: () => ASSTRING
asObject: () => ASOBJECT
}
interface BaseInterface extends TestInterface<any, any, any> { }
class Obj implements BaseInterface {
constructor(private n: number) {
}
asNum() {
return this.n;
}
asString() {
return this.n.toString();
}
asObject() {
return {value: this.n};
}
}
class Wrapper<T extends BaseInterface, ASNUM, ASSTRING, ASOBJECT> {
constructor(private obj: T & TestInterface<ASNUM, ASSTRING, ASOBJECT>) {
}
asNum() {
return this.obj.asNum() as ASNUM;
}
asString() {
return this.obj.asString() as ASSTRING;
}
asObject() {
return this.obj.asObject() as ASOBJECT;
}
}
let w = new Wrapper(new Obj(5));
let myNum = w.asNum(); // type: number
let myString = w.asString(); // type: string
let myObject = w.asObject(); // type: {value: number}
类型没问题!
我在 Typescript 2.3 的文档/即将推出的功能中没有找到很多关于此或可以帮助的内容.关于可能有助于形成更好解决方案的事情:
I didn't find a lot of things about that or that could help in the docs/upcoming features of Typescript 2.3. Concerning the things that could possibly help to shape a better solution:
- 这里有一篇关于可变类型的帖子,也许这可以帮助改进这样的示例(虽然不确定):https://github.com/Microsoft/TypeScript/issues/5453
- 关于
this
引用,这里提到了在使用--noImplicitThis
编译选项和ThisType
时强输入 this代码> 函数来明确声明这一点.但显然,与遵循对象模型流程相比,它更多地是关于了解其嵌入结构类型的函数.这对我来说没有帮助.
- There is a post about variadic types here, maybe this could help to improve such a sample (not sure though): https://github.com/Microsoft/TypeScript/issues/5453
- Concerning the
this
references, there's a mention about strongly typing this here when using the--noImplicitThis
compilation option, and theThisType<T>
function to declare this explicitely. But apparently it's more about a function being aware of its embedding structure type, than following the object model flow. And it doesn't help in my case.
这篇关于打字稿类型、泛型和抽象类的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!