我有模板名称enum,对于每个枚举值,函数fetchTemplate必须返回它自己的响应类型.

enum KeyEnum {
  VK = 'VK',
  VIBER = 'VIBER',
}

type VkResponse = { vk: string };
type ViberResponse = { viber: number };

type ReturnTypes = {
  [KeyEnum.VK]: VkResponse;
  [KeyEnum.VIBER]: ViberResponse;
};

我用这个签名创建了函数fetchTemplate:

const fetchTemplate = <T extends KeyEnum>(key: T): ReturnTypes[T]

在这具身体上:

const fetchTemplate = <T extends KeyEnum>(key: T): ReturnTypes[T] => {
  if (key === KeyEnum.VK) {
    return { vk: 'someString' }; <<- Here i got TS2322 error (description below)
  }

  if (key === KeyEnum.VIBER) {
    return { viber: 1 } as ReturnTypes[T]; <<- No problems, but i dont want use "as"
  }
};

# Unnecessary type definition in the left, just for testing
const vkRes: VkResponse = fetchTemplate(KeyEnum.VK);
const viberRes: ViberResponse = fetchTemplate(KeyEnum.VIBER);

但在函数体中,我不能只返回{vk: 'someString'}{viber:1},因为我得到了TS2322:

TS2322:
Type  { vk: string; }  is not assignable to type ReturnTypes[T]
Type  { vk: string; }  is not assignable to type  VkResponse & ViberResponse
Property  viber  is missing in type  { vk: string; }  but required in type ViberResponse 

为什么在这种情况下,TypeScrip不能进行文字缩小?为什么我需要使用"as ReturnTypes[T]",如何避免?如何正确地将key类型从T缩小到特定的.VK.VIBER,并在IFS块中显式定义某些(不是联合的)返回类型?

TypeScript v5.2.2
Node v20.8.0

这个也不起作用:

  switch (key) {
    case KeyEnum.VIBER:
      return { viber: 1 }; <<- The same TS2322 error
  }

推荐答案

目前control-flow based narrowing(如switch/caseif/else块)与generics不能很好地发挥作用. 问题是,虽然判断key === KeyEnum.VK将缩小key的类型,但它将影响not泛型type parameter(在您的示例中为T). 这被认为是一个缺失的功能,如microsoft/TypeScript#33014中所要求的. 在此之前,除非它被实现,否则你需要围绕它工作.

现在,如果你想使用一个泛型函数并返回一个indexed access type(如ReturnTypes[T]),你需要实际执行一个索引操作:也就是说,获取一个类型为ReturnTypes的对象,并读取类型为T的键处的属性. 从概念上讲,对于您的示例,它看起来像:

const fetchTemplate = <T extends KeyEnum>(key: T): ReturnTypes[T] => {
  return {
    [KeyEnum.VK]: { vk: 'someString' },
    [KeyEnum.VIBER]: { viber: 1 }
  }[key]
}; // okay

这样做可能存在的一个问题是,它需要预先计算每个可能的返回值.如果调用fetchTemplate(KeyEnum.VK),对象仍将计算{viber: 1},然后将其丢弃.对于这样一个简单的 case ,这可能没什么大不了的,但如果您的代码有副作用或计算成本很高,那么您可以重构为使用getters,这样只运行相关的代码:

const fetchTemplate = <T extends KeyEnum>(key: T): ReturnTypes[T] => {
  return {
    get [KeyEnum.VK]() {
      return { vk: 'someString' };
    },
    get [KeyEnum.VIBER]() {
      return { viber: 1 };
    }
  }[key]
};

在这里,如果你呼叫fetchTemplate(KeyEnum.VK),只有KeyEnum.VK的获取器永远不会运行.

希望有一天微软/TypeScrip#33014会被实现,因为这种重构虽然功能强大,但可能会令人困惑.

Playground link to code

Typescript相关问答推荐

类型脚本强制泛型类型安全

具有泛型类型和缺省值的键

Material UI / MUI系统:我如何告诉TypeScript主题是由提供程序传递的?

在对象中将缺省值设置为空值

错误TS2403:后续变量声明必须具有相同的类型.变量';CRYPTO';的类型必须是';CRYPATO';,但这里的类型是';CRYPATO';

在类型脚本中创建泛型类型以动态追加属性后缀

如何在ANGLE中注册自定义验证器

如何编写在返回函数上分配了属性的类型安全泛型闭包

API文件夹内的工作员getAuth()帮助器返回:{UserID:空}

类型脚本-在调用期间解析函数参数类型

如何定义这样一个TypeScript泛型,遍历路由配置树并累积路径

从以下内容之一提取属性类型

如何使用警告实现`RecursiveReadonly<;T>;`,如果`T`是原语,则返回`T`而不是`RecursiveReadonly<;T>;`

仅当类型为联合类型时映射属性类型

Typescript不允许在数组中使用联合类型

在tabel管理区域react js上设置特定顺序

Typescript循环函数引用

数据库中的表请求返回如下日期:2023-07-20T21:39:00.000Z 我需要将此数据格式化为 dd/MM/yyyy HH:mm

在Nestjs中,向模块提供抽象类无法正常工作

具有来自其他类型的修改键名称的 TypeScript 通用类型