问题是

在TypeScrip中,我们可以使用extends来断言一个泛型参数作为另一种类型的‘子类型’.例如.

// class Base {}
// class Child extends Base {}
// edited:
class Base { a = 1 }
class Child extends Base { b = 2 } // Child is a sub class of Base

class Test {
  // assert T is a subtype of Base
  hello<T extends Base>() { ... }
}

new Test().hello<Child>();

但是如何将泛型参数断言为另一种类型的"超类型"呢?例如.

class Test2 {
  // HERE, how to ensure T is a super type of Child
  hello<T ??? Child>() { ... }
}

new Test2().hello<Base>();

用例

// class Actions<T = unknown> {
// edited:
class Actions<T = never> {
  execs: (() => T)[]

  append(exec: () => T) {
    this.execs.push(exec)
    return this;
  }

  // HERE, how to ensure NewT is a super type of T?
  setT<NewT ??? T>() {
    return this as Actions<NewT>;
  }
}

// to make the following work
new Actions()
  .setT<Child>()
  .append(() => new Child())
  // before this line, all exec should return Child
  // after this line, all exec should return Base
  .setT<Base>()
  .append(() => new Base())
  .execs.map(e => e()) // => Base[]

如果我们仍然像这样使用extends:

setT<NewT extends T>() {
  return this as Actions<NewT>;
}

收到错误:

Conversion of type 'this' to type 'Actions<NewT>' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
  Type 'Actions<T>' is not comparable to type 'Actions<NewT>'.
    Type 'T' is not comparable to type 'NewT'.
      'NewT' could be instantiated with an arbitrary type which could be unrelated to 'T'.ts(2352)

是的,我可以使用unknown来转换返回类型,但是函数setT的签名不能表达我们想要的类型约束.

推荐答案

目前在Typescript 中没有直接指定lower bound generic constraint的方法.现在,我们只有upper bound个表单T extends U的约束,这些约束要求T必须是U的子类型.表单T super U没有类似的约束,强制要求T必须是U的超类型.在microsoft/TypeScript#14520有一个开放的特性请求,如果您想要实现这一点,您可能想要向该问题添加一个👍.但现在,它不是语言的一部分,您需要解决它.

一种可能的解决方法是在表单T extends (U extends T ? unknown : never)的约束中使用conditional type.它看起来很奇怪,但U extends T是您真正想要强制实施的约束,因此,如果满足约束,条件类型U extends T ? unknown : never的计算结果将为unknown,如果不满足,则计算结果为never.因此,如果T super U将成功(因为所有类型都扩展unknown),则T extends (U extends T ? unknown : never)将成功,如果T super U将失败,则T extends (U extends T ? unknown : never)将失败,主要是(除never之外没有类型扩展never,因此如果T不是never,则这将按预期行为...但如果T等于never,就无法阻止它被接受.这是一种变通方法,所以🤷‍♂️).

对于你的Base/Child,这看起来像是

class Base { a = 1 }
class Child extends Base { b = 2 }
class Grandchild extends Child { c = 3 }
class Test2 {
  hello<T extends Child extends T ? unknown : never>() { }
}

new Test2().hello<Base>(); // okay
new Test2().hello<Grandchild>(); // error

请注意,您可以指定超类型,但不能指定子类型.然后Actions美元,你就能得到

class Actions<T = never> {
  execs: (() => T)[] = []

  append(exec: () => T) {
    this.execs.push(exec)
    return this;
  }

  setT<NewT extends ([T] extends [NewT] ? unknown : never)>() {
    return this as any as Actions<NewT>;
  }
}

除了T是一个类型参数,我必须将T extends NewT换成[T] extends [NewT]以避免将其设置为distributive conditional type之外,其他情况都类似.现在,以下方法奏效了:

new Actions()
  .setT<Child>()
  .append(() => new Child())
  .setT<Base>()
  .append(() => new Base())
  .execs.map(e => e()) // => Base[]

看起来不错 理想情况下,总有一天会有一个真正的下限约束,我们可以放弃变通办法.

Playground link to code

Typescript相关问答推荐

如何为ViewContainerRef加载的Angular组件实现CanDeactivate保护?

React重定向参数

在Typescript 中,有没有`index=unfined的速记?未定义:某个数组[索引]`?

如何将对象数组中的键映射到另一种对象类型的键?

复选框Angular 在Ngfor中的其他块中重复

在类型脚本中将泛型字符串数组转换为字符串字母并集

try 使Angular依赖注入工作

TS2739将接口类型变量赋值给具体变量

如何键入派生类的实例方法,以便它调用另一个实例方法?

如何为特定参数定义子路由?

类型实例化过深,可能是无限的&TypeScrip中的TupleUnion错误

错误TS7030:并非所有代码路径都返回值.";由tsfig.json中的";Strong";:False引起

当传递带有或不带有参数的函数作为组件props 时,我应该如何指定类型?

在REACT查询中获取未定义的isLoading态

为什么看起来相似的代码在打字时会产生不同的错误?

我的类型谓词函数不能像我预期的那样工作.需要帮助了解原因

可以将JS文件放在tsconfig';s includes/files属性?或者,我如何让tsc判断TS项目中的JS文件?

为什么类方法参数可以比接口参数窄

Select 器数组到映射器的Typescript 泛型

有没有办法从不同长度的元组的联合中提取带有类型的最后一个元素?