打字稿。根据枚举输入参数返回不同的对象类型

Typescript. Return different objects type depending on enum input argument

提问人:Mauzzz0 提问时间:11/14/2023 最后编辑:Mauzzz0 更新时间:11/15/2023 访问量:84

问:

我有模板名称 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);

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

TS2322:
类型 { vk: string; } 不可分配给 ReturnTypes[T]
类型 { vk: string; } 不可分配给类型 VkResponse 和 ViberResponse 属性 Viber 在类型 { vk: string; } 中缺失,但在类型 ViberResponse
中是必需的

为什么在这种情况下 TypeScript 不能进行类型缩小?为什么我需要使用“as ReturnTypes[T]”,如何避免它?我如何正确地将类型范围从特定缩小到特定或在 IF 块中显式定义某些(非联合)返回类型?keyT.VK.VIBER

TypeScript v5.2.2
节点 v20.8.0

这个也不起作用:

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

评论

0赞 Mina- 11/14/2023
你能检查一下吗?stackoverflow.com/questions/54165536/......也许这会有所帮助......
0赞 Mauzzz0 11/14/2023
@Mina- 我已经检查了我认为所有相同的问题,但没有找到我的情况的解决方案,所有其他情况,例如.我不喜欢这种结构,因为我认为没有必要,代码可以更清晰、更紧凑。T extends "first" ? FirstType : T extends "second" ? SecondType :
1赞 jcalz 11/15/2023
发生这种情况是由于 TS 中缺少一个功能;请参阅 ms/TS#33014。修复它的方法是重构为索引访问,可能使用访问器,这样您就不会预先计算每个可能的输出;像这样。这是否完全解决了这个问题?如果是这样,我会写一个答案来解释;如果没有,我错过了什么?
0赞 Mauzzz0 11/15/2023
@jcalz是的,非常感谢!这正是我想要的!
1赞 jcalz 11/15/2023
谢谢!当我有机会时,我会写下答案,大概在接下来的一个小时里

答:

0赞 Yosef Tukachinsky 11/14/2023 #1

您可能正在寻找的是函数重载,在您的情况下,它将如下所示:

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

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

function fetchTemplate(key: KeyEnum.VIBER): ViberResponse;
function fetchTemplate(key: KeyEnum.VK): VkResponse;
function fetchTemplate(key: KeyEnum) {
  if (key === KeyEnum.VK) {
    return { vk: 'someString' };
  }

  if (key === KeyEnum.VIBER) {
    return { viber: 1 }
  }
}

const vkRes: VkResponse = fetchTemplate(KeyEnum.VK);
const viberRes: ViberResponse = fetchTemplate(KeyEnum.VIBER);

评论

0赞 Mauzzz0 11/15/2023
我忘了说“函数”重载,它们不合适,代码风格不允许使用“函数”,只允许使用箭头函数。
0赞 Valentin 11/15/2023
@Mauzzz0 这个决定背后的理由是什么?
0赞 Mauzzz0 11/15/2023
@Valentin idk,它就在我面前,我只需要握住它
0赞 Valentin 11/15/2023
这是我第一次听说这样的规则。不使用经典函数可能是一个问题:例如,断言函数不能写成箭头函数。如果没有人知道为什么,这条规则可能应该重新审视。
2赞 jcalz 11/15/2023 #2

目前,基于控制流的缩小(如/或/块)不能很好地与泛型一起使用。问题在于,虽然检查会缩小 的类型,但它不会影响泛型类型参数(在您的示例中)。根据 microsoft/TypeScript#33014 中的要求,这被视为缺少的功能。除非实现,否则您需要解决它。switchcaseifelsekey === KeyEnum.VKkeyT

现在,如果要使用泛型函数并返回索引访问类型,例如 ,则需要实际执行索引操作:即获取类型的对象,并在 类型的键处读取属性。从概念上讲,对于您的示例,如下所示:ReturnTypes[T]ReturnTypesT

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

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

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

在这里,如果你调用 ,只有 getter for 永远不会运行。fetchTemplate(KeyEnum.VK)KeyEnum.VK

希望有一天 microsoft/TypeScript#33014 能够实现,因为这种重构虽然功能强大,但可能会令人困惑。

Playground 代码链接