【问题标题】:Typescript typings, generics and abstract classes打字稿类型、泛型和抽象类
【发布时间】:2017-09-15 01:13:40
【问题描述】:

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

让我们考虑以下示例 (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&lt;Foo&gt;(foo: Foo): {}

通过这个示例,更清楚的是,即使知道FOO 的类型为FooTypescript 使用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 参数类型中的内容可以变得更简洁。

【问题讨论】:

  • 嗯,将const p = str =&gt; console.log(JSON.stringify(str)); p(result2); p(result3); p(result4); 添加到您的测试中会将3 x {"bar":"bar"} 添加到控制台中。无法重现您正在谈论的问题。请指教
  • @jevgenig 这不是关于值,而是关于它们的类型(以及因此类型检查、完成等......)。要观察问题,请单击 Typescript 游乐场链接(或粘贴到 IDE,例如 VSCode 或 WebStorm)并将鼠标光标放在相关变量上。
  • 我现在明白了,感谢您的澄清。 result2 具有 any 类型,因为 abstract bar 在您的 FooAbstract 类中没有定义返回类型。尝试将其更改为abstract class FooAbstract { abstract bar(): { bar: string }; }
  • @jevgenig 感谢您的建议。但是我不知道如何使它工作。而是更好地理解泛型能做什么和不能做什么,以及设计限制。

标签: generics typescript abstract typescript-typings


【解决方案1】:

这是一个明确的答案和传递方法返回类型所需的示例。

问题

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

换句话说,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 的文档/即将推出的功能有所帮助的内容。关于可能有助于形成更好解决方案的事情:

  • 这里有一篇关于 variadic types 的帖子,也许这有助于改进这样的示例(虽然不确定):https://github.com/Microsoft/TypeScript/issues/5453
  • 关于this 引用,在使用--noImplicitThis 编译选项和ThisType&lt;T&gt; 函数显式声明时,这里提到了强类型。但显然它更多的是关于一个函数了解其嵌入结构类型,而不是遵循对象模型流程。在我的情况下它没有帮助。

【讨论】:

    【解决方案2】:

    这就是关于 bar 函数的类型解析如何工作的全部内容:

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

    this.foo 是什么? FOO 或更准确地说,是扩展 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);
    

    【讨论】:

    • :'( 我害怕的答案。如果我理解得很好,如果FooAbstractbar()等10个函数,或者如果有很多不同的类扩展FooAbstract(如果@ 987654333@ 是一个更复杂的工厂),我的代码会变得一团糟。或者我不得不忘记打字。对吧?
    • 您能解释一下“必须在定义实际类型 FOO 之前确定类型”是什么意思吗?不知道我得到了确切的类型是如何计算的,或者它们的限制是什么。
    • 属性要么是隐式类型,要么是显式类型。bar 没有显式类型,所以使用隐式类型,即 FooAbstract.bar 的类型 {}。 Foo 的类型为 FOO,它是泛型类型,并定义为这样
    • 如果你所做的只是扩展一个现有类型,你可以直接转换成它:`var result = (new FooMaker(new Foo) as Foo).bar() // works跨度>
    • 是的,我得到了隐式和显式类型。但我期待 Typescript 能够更好地隐含推断 FOO 的类型。鉴于FOO 的实际类型是通过构造函数传递的,result1 是可以的,result2 的结果也差不多。不,我想做的不仅仅是打电话给bar()。否则会有更简单的解决方案。对象范式优于原型范式、象形文字语法以及很难对最常见的模式进行打字,我有时想知道为什么我使用 Typescript……嗯,谢谢你的回答,让我们验证一下。
    猜你喜欢
    • 1970-01-01
    • 2021-11-26
    • 1970-01-01
    • 2020-01-23
    • 2019-01-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-12
    相关资源
    最近更新 更多