打字稿类型,泛型和抽象类

时间:2017-04-19 01:59:27

标签: generics typescript abstract typescript-typings

我尝试了一种对我来说很奇怪的行为。

让我们考虑以下示例(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

result2result3的输入类似于抽象bar函数({})。似乎this未被解析为具体类Foo,而是作为抽象类FooAbstract。而foo2的类型显示正确解析了类foo属性。

发生了什么事?我做错了吗?

更新

作为事后的想法,这个案例可以像(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<Foo>(foo: Foo): {}

通过此示例,更清楚的是,即使知道FOO被键入为Foo Typescript 也会使用FOO定义而不是其显式类型bar返回类型。

更新2

好的,在打字时打架,我觉得我平了。确实,即使推断出类型, Typescript 中的隐式输入也不遵循任何继承模型。好吧,我仍然想知道为什么它会改变,但我必须应对“它就像那样”。所以在这种情况下,类型必须是明确的。

我找到了一种更简单的方法来编写他的例子(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参数类型中的内容可以变得更清晰。

2 个答案:

答案 0 :(得分:1)

这就是类型分辨率如何适用于条形函数:

bar() { 
    return this.foo.bar();
}

什么是this.fooFOO或更确切地说,是一个扩展FooAbstract的类,因为与属性foo不同,bar不会公开FOO。必须在定义实际类型FOO之前确定打字。

如果你真的想输入它,你必须做这样的事情:

abstract class FooAbstract<T extends {}> {
    abstract bar(): T
}

class Foo extends FooAbstract<{ bar: string }> { 
    bar() { 
        return { bar: 'bar' };
    }
}

class FooMaker<FOO extends FooAbstract<BAR>, BAR> {  
    constructor(public foo: FOO) {}

    bar():BAR { 
        return this.foo.bar();
    }

    baz = (): BAR => {
        return this.foo.bar();
    }
}

let foo = new Foo();
let result = foo.bar();

let foomaker = new FooMaker<Foo, { bar: string}>(new Foo);
let foo2 = foomaker.foo; // Type "Foo", OK
let result1 = foomaker.foo.bar(); // Type "{bar: string}", OK
let result2 = foomaker.bar(); // Type "{bar: string}", OK
let result3 = foomaker.baz(); // Type "{bar: string}", OK

不幸的是,你必须明确定义FooMaker的类型,但你确实阻止了这样的事情:

let foomaker = new FooMaker<Foo, { bar: number}>(new Foo);

答案 1 :(得分:0)

以下是传递方法返回类型所需内容的简洁答案和示例。

问题

嵌入另一个对象的对象使用其内部声明的类型(在本例中为抽象类型)来确定其函数返回类型。即使已知(或明确声明)该对象类型。

换句话说,Typescript类型推断不会在对象方法中查看推断类型。

解决方案

我发现处理这种情况的唯一解决方案是将泛型与方法/函数返回类型相关联,并将对象结构与它们相匹配。

根据我的问题更新2 test it in 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 的文档/即将推出的功能的内容。关于可能有助于形成更好解决方案的事情:

  • 这里有关于可变参数类型的帖子,也许这可以帮助改进这样的样本(不确定):https://github.com/Microsoft/TypeScript/issues/5453
  • 关于this引用,在使用--noImplicitThis编译选项和ThisType<T>函数明确声明时,会提到强类型。但显然它更多的是一个函数意识到它的嵌入结构类型,而不是跟随对象模型流。在我的情况下它没有用。