我正在做一个打字项目,我遇到了一个似乎无法解决的打字错误.以下是代码的简化版本:

type OptionMode = 'A' | 'B' | 'C';

const Foo = <Mode extends OptionMode>(
  mode: Mode
): Mode extends 'A' ? string : number => {
  // Your implementation here

  // Example implementation:
  if (mode === 'A') {
    return 'This is a string';// ERROR: Type 'string' is not assignable to type 'Mode extends "A" ? string : number'.(2322)
  } else {
    return 1 // ERROR:Type 'number' is not assignable to type 'Mode extends "A" ? string : number'.(2322)

  }
};


const stringResult = Foo( 'A');
const numberResult= Foo( 'B');

在这段代码中,我try 创建一个foo函数,它根据输入模式返回一个字符串或一个数字.但是,我收到以下错误:

  • Type 'string' is not assignable to type 'Mode extends "A" ? string : number'.(2322)
  • Type 'number' is not assignable to type 'Mode extends "A" ? string : number'.(2322)

我很难理解为什么会发生这些错误,以及如何修复它们.有没有人能帮我弄清楚哪里出了问题,以及如何让这段代码按预期工作?

谢谢!

推荐答案

通过缩小设置为该泛型类型的变量的类型,TypeScrip无法缩小泛型类型.当使用具有引用其泛型类型的多个参数的泛型函数时,这不可能严格执行的原因变得更加清楚.例如:

type OptionMode = 'A' | 'B' | 'C';

declare const Foo: <Mode extends OptionMode>(
  first: Mode,
  second: Mode
) => Mode extends 'A' ? string : number;

const stringResult = Foo('A', 'A'); // <- `Mode` is inferred as 'A'
const numberResult = Foo('A', 'B'); // <- `Mode` is inferred as 'A' | 'B'

TypeScript Playground

正如本例所示,由于您的泛型类型可以是包含多个OptionMode成员的联合,因此TypeScrip无法根据其参数之一的运行时类型来缩小它的范围.

在类型脚本5.3中可能会有一个变化,通过缩小参数范围将达到allow some narrowing of generic types,但这种缩小将以将泛型类型设置为lower limit的形式出现.例如,如果mode参数的值为'A',那么Mode肯定是'A'类型,但它仍然可以是包含该类型的并集.

当函数参数需要一起缩小范围时,可以通过使用带有元组类型的扩展语法来定义它们来解决这一问题,其中该元组是有区别的联合.这样,当只缩小了多个参数中的一个参数的类型时,Type脚本就可以缩小这些参数的类型.但在这种情况下,需要更改的是函数的返回类型,这是不可能的.

这就是重载工作的原因,正如您发布的答案所示.但值得一提的是,超载可能很难维护.TypeScrip不会判断您的实现是否与所有重载匹配,如果您有很多重载,则需要手动写出它们.

您可以使用的另一种方法包括定义一个类型,该类型根据每个OptionMode指定一个返回类型,然后从泛型函数索引到该类型,而不是使用条件类型.这仍然不是一个完美的解决方案-例如,它具有与联合类型相同的潜在问题,并且在我提到的更新可能在类型脚本5.3中出现之前,它需要类型断言--但在某些情况下,它可能比基于重载的解决方案更容易维护.

type OptionMode = 'A' | 'B' | 'C';

type ModeData = {
  A: string;
  B: number;
  C: number;
};

const Foo = <Mode extends OptionMode>(
  mode: Mode
): ModeData[Mode] {
  if (mode === 'A') {
    return 'This is a string' as ModeData[Mode];
  } else {
    return 1 as ModeData[Mode];
  }
}

const stringResult = Foo('A'); // <- string
const numberResult = Foo('A'); // <- number

TypeScript Playground

如您所见,在本例中返回值时,我必须使用as ModeData[Mode].但是,在我提到的"下限"更改发布之后,应该能够删除这些类型断言.

Typescript相关问答推荐

如何根据服务响应构建子路由

VS代码1.88.0中未出现自动导入建议

Redux—Toolkit查询仅在不为空的情况下添加参数

基于键的值的打字动态类型;种类;

如何表示多层深度的泛型类型数组?

TypeScrip 5.3和5.4-对`Readonly<;[...number[],字符串]>;`的测试版处理:有什么变化?

如何解决类型T不能用于索引类型{ A:typeof A; B:typeof B;. }'

记录键的泛型类型

如何使所有可选字段在打印脚本中都是必填字段,但不能多或少?

迭代通过具有泛型值的映射类型记录

APP_INITIALIZER在继续到其他Provider 之前未解析promise

如何正确调用创建的类型?

在组件卸载时try 使用useEffect清除状态时发生冲突.react +打字

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

如何使用TypeScrip在EXPO路由链路组件中使用动态路由?

在单独的组件中定义React Router路由

在Google授权后由客户端接收令牌

Select 类型的子项

如何使用动态生成的键和值定义对象的形状?

如何确定单元格中的块对 mat-h​​eader-cell 的粘附(粘性)?