如何为TypeScript泛型方法进行类型推理

pxyaymoc  于 2023-03-04  发布在  TypeScript
关注(0)|答案(1)|浏览(130)

我试图用TypeScript重写一些C#代码,但没有用。除非我直接提供基类,否则无法推断响应类型。

class CommandParameter {
  readonly value: string = "value";
}

class Command<T> {}

class SubCommand<T> extends Command<T> {}

class Client {
  execute<T>(command: Command<T>): T {
    return null as T;
  }
}
//TS2339: Property 'value' does not exist on type 'unknown'.
const valueError = new Client().execute(new SubCommand<CommandParameter>()).value; //<--error
const valueOk = new Client().execute(new Command<CommandParameter>()).value;

使用Typescript是否有可能实现所需的行为(使推理为子类工作)?是否有任何类似的替代模式?

nbnkbykc

nbnkbykc1#

TypeScript的类型系统很大程度上是结构化的,而不是nominal。类型是通过它们的形状/结构来比较的,而不是通过它们的名称/声明。

class Command<T> {}
class SubCommand<T> extends Command<T> {}

在结构上彼此相同,并且与空对象类型相同,并且在结构上独立于它们的generic类型参数。因此Command<string>SubCommand<number>以及Command<Date>SubCommand<boolean>都是 * 相同类型 *。
这使得推理表现不佳;有时候编译器确实可以通过声明的模式匹配从Command<T>中推断出T,就好像它有一个名义类型系统一样,但是即使这样做也是非常脆弱的,一旦编译器执行了结构比较,那么就无法从{}推断T。这个推断问题在TypeScript FAQ条目中描述为什么类型推断不能在此接口上工作:interface Foo<T> { }
一般来说,修复方法是使任何泛型类型在结构上依赖于其泛型类型参数。例如:

class Command<T> {
    x;
    constructor(x: T) {
        this.x = x;
    }
}

class SubCommand<T> extends Command<T> {
}

现在Command<T>有一个T类型的x属性,SubCommand<T>也是这样,因为它继承自Command<T>。所以现在这些类型不再与{}相同,而是与{x: T}相同。从{}推断T通常是不可能的。但是从{x: T}推断T非常简单。现在一切都正常了:

const v1 = new Client().execute(new SubCommand(new CommandParameter())).value;
const v2 = new Client().execute(new Command(new CommandParameter())).value;

显然,给一个类一个类型为泛型类型参数的属性并不总是适用的,但希望您实际上在类中的某个地方处理T类型的值,以便Command<T>在结构上 * 以某种方式 * 依赖于T。或者你可能不得不用dummy/phantom属性来模拟nominal类型,但是这些都超出了这个问题的范围,所以我在这里就不多说了。
Playground代码链接

相关问题