typescript 类型脚本,根据枚举输入参数返回不同的对象类型

2guxujil  于 2023-11-20  发布在  TypeScript
关注(0)|答案(2)|浏览(138)

我有模板名enum,对于每个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},因为我得到了TS 2322:
TS2322:
类型{ vk:string; }不能分配给类型ReturnTypes[T]
类型{ vk:string; }不能分配给类型VkResponse & ViberResponse
类型{ vk:string; }中缺少属性viber,但ViberResponse类型中需要该属性
为什么TypeScript在这种情况下不能做类型收缩?为什么我需要使用“as ReturnTypes[T]",如何避免它?我如何正确地将key类型从T缩小到特定的.VK.VIBER,并在IF块中显式定义某些(非联合)返回类型?
TypeScript v5.2.2
节点v20.8.0
这个也不行:

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

k4aesqcs

k4aesqcs1#

当前基于控制流的收缩(如switch/caseif/else块)不能很好地处理generics。问题是,虽然检查key === KeyEnum.VK会缩小key的类型,但它不会影响泛型类型参数。(在您的示例中是T)。这被认为是一个缺失的特性,正如microsoft/TypeScript#33014中所要求的那样。除非实现了它,否则您需要解决它。
现在,如果你想使用一个泛型函数并返回一个ReturnTypes[T]这样的indexed access type,你需要实际执行一个索引操作:也就是说,获取一个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},然后把它扔掉。对于这样一个简单的情况,这可能没什么大不了的,但是如果你的代码有副作用或者计算起来很昂贵,那么你可以重构为使用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的getter永远不会运行。
希望有一天microsoft/TypeScript#33014能够实现,因为这种重构虽然功能强大,但可能会引起混淆。
Playground链接到代码

koaltpgm

koaltpgm2#

你可能需要的是函数重载,在你的例子中看起来像这样:

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);

字符串

相关问题