【问题标题】:Typescript parent class method return type change by child propertyTypescript父类方法返回类型由子属性更改
【发布时间】:2021-02-22 23:35:11
【问题描述】:

有没有办法让类的父类使用的方法返回准确的类型(当它用于派生类型的属性发生更改时)?

例子:

abstract class A {
  a: string

  constructor(a: string) {
    this.a = a;
  }
}


class B extends A {
  b: string

  constructor(a: string, b: string) {
    super(a);
    this.b = b;
  }
}


abstract class AColl {
  abstract itemsMap: Map<string, A>

  items() {
    return [...this.itemsMap.values()];
  }
}

class BColl extends AColl {
  itemsMap: Map<string, B>
}

const bColl = new BColl();

bColl.items() -> Map<string, A> instead of Map<string,B>

在这个例子中,Intellisense 认为这是 Map 而不是 Map,虽然我认为它能够派生出正确的类型,但我可以得到它(我可能很天真)。

有没有办法在不重新实现子类的方法的情况下实现这一点?

【问题讨论】:

    标签: typescript inheritance


    【解决方案1】:

    为什么不直接参数化基类:

    abstract class AColl<V extends A = A> {
        itemsMap: Map<string, V>; // no need to be abstract when class'es type param is used.
    
        items(): V[] {
            return [...this.itemsMap.values()];
        }
    }
    
    class BColl extends AColl<B> {
        // no need to have itemsMap field - extends AColl<B> does the job.
    }
    
    const bColl = new BColl();
    bColl.items()
    

    【讨论】:

    • 太棒了!非常感谢。 (刚开始尝试使用 Typescript,所以不知道参数化的东西 - 会再研究一下)
    • 相关文档链接:generic classes
    【解决方案2】:

    ACollitems方法体内,类型

    [...this.itemsMap.values()]
    

    热切地评估为Array&lt;A&gt;。当您开始在 this 上查找属性时,编译器会将 A 替换为 this。这种急切的替换通常是有帮助的,因为它会为事物产生特定的类型,否则编译器将不得不推迟对任何东西的评估,因为有人关心具有更特定类型的子类。而且这种延迟类型往往很难使用;编译器通常无法验证它们的可分配性。

    但是,在您的情况下,this.itemsMap 的计算结果为Map&lt;string, A&gt;,您的希望破灭了; AColl 的所有子类将为 items() 返回 Array&lt;A&gt;


    相反,您想要将this 视为“任何this 恰好在AColl 的任何子类中调用此方法”。 TypeScript 中已经存在这样的概念:“polymorphic this”;你可以只使用this 作为类型。

    您可以将多态this 视为一种“隐式泛型类型参数”。也就是说,您可以通过将AColl generic 设为AColl&lt;T&gt;,然后稍后引用T 来达到类似的效果。有时,显式泛型可能是继续进行的方式(我在这里看到另一个答案),但当您想说“this 是什么”时,它们并不总是必要的。

    所以,您希望this 保持this 类型,但是一旦您开始对它进行索引,编译器就会急切地用A 替换它。避免这种情况的唯一方法是在items() 的返回值上使用type assertion。你会断言返回值是你计算的类型,它取决于this

    items() {
        return [...this.itemsMap.values()] as
            Array<this["itemsMap"] extends Map<string, infer C> ? C : never>;
    }
    

    即使用lookup typesconditional type inference 表示“获取this,查看它的itemsMap 属性,找出它存储的值类型,然后返回其中的Array”。现在,这将在 BColl 中随心所欲地工作:

    const bColl = new BColl();
    bColl.itemsMap.set("foo", new B("foo", "bar"))
    console.log(bColl.items()[0].b.toUpperCase()); // BAR
    

    Playground link to code

    【讨论】:

      猜你喜欢
      • 2021-07-28
      • 2021-04-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-11
      相关资源
      最近更新 更多