我注意到一个非常奇怪的行为,它试图拥有一个带有泛型参数的子类.

// no error for this code
abstract class BaseClass<Input extends object, Output extends object> {
  public abstract execute(input: Input): Output | void
}

class ChildClass extends BaseClass<{ content: string }, {}> {
  public execute(input: {content: string }){
    return true;
  }
}

这段代码没有编译错误.然而,我可以让内容不是强制性的,Typescript 也会很高兴

class ChildClass extends BaseClass<{ content?: string }, {}> {
  public execute(input: {content: string }){
    return true;
  }
}

我不知道这种行为是否正常.

真正奇怪的是,如果我向基类添加一个随机键,那么类型判断就会像我预期的那样工作:

abstract class BaseClass<Input extends object, Output extends object> {
  // Adding any mandatory key here will do but not an empty object
  public abstract execute(input: Input & { __trustMeImNecessary: true }): Output | void
}

class ChildClass extends BaseClass<{ content?: string }, {}> {
  public execute(input: {content: string }){
    return true;
  }
}

现在我得到了 Type '{ content?: string | undefined; } & { __trustMeImNecessary: undefined; }' is not assignable to type '{ content: string; }'.,如果我强制使用content,那就没问题了.

这种行为正常吗?如果可以的话,我真的可以使用这个黑客吗?

Reproduction in the typescript playground

推荐答案

相关定义见Difference between Variance, Covariance, Contravariance and Bivariance in TypeScript.

打字脚本判断method个参数bivariantly.这意味着它既接受safe个子类方法,其中widen个参数类型(contravariantly),也接受unsafe个子类方法,narrow个参数类型(covariantly).后一种行为在某种程度上是有用的,但您并不满意.


如果您启用了the --strictFunctionTypes compiler option个,那么非方法函数将以安全的方式进行判断(它们的参数是逆变量).但没有办法强制对方法进行严格的判断.您可以将方法更改为函数值实例属性,并获得所需的严格性:

abstract class BaseClass<I extends object, O extends object> {
  abstract execute: (input: I) => O | void
}

class ChildClass extends BaseClass<{ content?: string }, {}> {
  execute = (input: { content: string }) => true // error
}

class ChildClass2 extends BaseClass<{ content: string }, {}> {
  execute = (input: { content: unknown }) => true // okay
}

目前,这里没有非黑客使用方法.您可能会想到将超类方法声明为函数值属性,并用方法覆盖它,但这是不允许的.在microsoft/TypeScript#51261有一个特性请求,明确允许用方法覆盖abstract个函数值属性,但目前它还不是语言的一部分.

Hack的工作原理是使超类参数成为原始参数类型Ibranded个子类型.子类很容易做到安全的事情,因为I的任何超类型都是I & {__brand: true}的超类型.但现在做不安全的事情更难了,因为现在你必须找到Isubtype,它要么是I & {__brand: true}的超类型,要么是I & {__brand: true}的子类型.大多数涉及实现方法的方法都使用标记键(在这里是__brand,在您的示例代码中是__trustMeImNecessary),我们认为这是不可能的.可能会有一些边缘情况,但第一次排序会使其行为符合预期.

Playground link to code

Typescript相关问答推荐

在角形加载组件之前无法获取最新令牌

如何传递基于TypScript中可变类型的类型?

使用前的组件渲染状态更新

类型断言添加到类型而不是替换

react 路由6操作未订阅从react 挂钩表单提交+也不使用RTK查询Mutations

如何键入函数以只接受映射到其他一些特定类型的参数类型?

如何重构长联合类型?

接口中函数的条件参数

为什么有条件渲染会导致material 自动完成中出现奇怪的动画?

如何将v-for中的迭代器编写为v-if中的字符串?

如何创建一家Reduxstore ?

是否可以判断某个类型是否已合并为内部类型?

TYPE FOR ARRAY,其中每个泛型元素是单独的键类型对,然后在该数组上循环

我怎样才能正确地输入这个函数,使它在没有提供正确的参数时出错

如何从一个泛型类型推断多个类型

Typescript将键数组映射到属性数组

Typescript泛型调用对象';s方法

使用嵌套属性和动态执行时,Typescript 给出交集而不是并集

React router - 如何处理嵌套路由?

如何使用布尔值构建对象 TYPE,但至少有一个必须为 true