具有动态返回类型的泛型函数

时间:2021-03-03 15:58:26

标签: typescript typescript-generics

诚然,我对 TypeScript 还很陌生,我正在尝试弄清楚如何创建一个通用共享库函数 getFlags,它接受​​一个字符串标识符数组并返回一个对象,其键是标识符,其属性是标志对象。除了它们的 config 属性...外,所有标志对象都具有相似的形状。

例如:

const flags = getFlags(['flagA', 'flagB'])

结果 flags 看起来像这样

{
  flagA: {
   name: 'a',
   value: 'test1',
   config: {
     foo: 'red'
   }
  },
  flagB: {
   name: 'b',
   value: 'test2',
   config: {
     bar: 123
   }
  },
  // ....
}

我最初尝试为此定义类型的尝试如下所示:

type Flag<T> {
  name: string,
  value: string,
  config: T
}

type FlagList = {
  [key: string]: Flag<unknown>
}

type ConfigA {
  foo: string
}

type ConfigB {
  bar: number
}

我想要的是一种创建 getFlags 的通用版本的方法,以便 Typescript 知道返回的每个标志的基础配置属性的类型。

const {flagA, flagB} = getFlags(['flagA','flagB'])

flagA.config.foo // OK
flagA.config.bar // Invalid
flagB.config.bar // OK
flagB.config.foo // Invalid

const {flagA, flagB, flagC, flagD} = getFlags(['flagA','flagB' 'flagC', 'flagD'])

getFlags 函数看起来像这样:

function getFlags(ids: string[]): FlagList {
  return retrieveFlagsByIds(ids)
}

但是,我不确定如何告诉 TypeScript 配置属性的类型信息。一种想法是像这样以某种方式传递类型。然而,可能有任意数量的这些标志,这似乎有点疯狂:

const { flagA, flagB } = getFlags<ConfigA, ConfigB>(['flagA','flagB'])

const { flagA, flagB, flagC, flagD, flagE } = getFlags<ConfigA, ConfigB, ConfigC, ConfigD, ConfigE>(['flagA','flagB', 'flagC', 'flagD', 'flagE'])

因为 getFlags 是一个共享库函数,所以不可能知道消费者可能拥有的所有标志配置类型。有什么想法吗?

1 个答案:

答案 0 :(得分:3)

我倾向于创建一个辅助对象类型 FlagConfigs 来表示标志名称和配置类型之间的映射:

interface FlagConfigs {
  flagA: { foo: string };
  flagB: { bar: number };
  flagC: { baz: boolean }
}

那么 getFlags() 的调用签名将如下所示:

declare function getFlags<K extends keyof FlagConfigs>(flags: K[]): { 
  [P in K]: Flag<FlagConfigs[P]> 
};

这里我们在 K 中创建函数 genericFlagConfigs 的键的 union。这个联合看起来像 "flagA" | "flagB" 等,所以它会为 flags 数组中的每个不同值都有一个成员,这就是处理可能无限数量类型的问题的原因参数。单个 K 类型参数可以表示所有这些。对于返回类型,我们将 K 通过 mapping 中的每个标志类型 P 转换为一个属性,其中 P 是键,其中 Flag<FlagConfigs[P]> 是值类型。后一种类型的意思是“在 P 中查找 FlagConfigs 键,获取该属性,并将其用作 config 中的 Flag 属性类型。

让我们看看它是否有效:

const {flagA, flagB} = getFlags(['flagA','flagB'])
flagA.config.foo // okay
flagA.config.bar // error!
flagB.config.bar // okay
flagB.config.foo // error!

看起来不错。

至于 getFlags()实现,这取决于您。您可能需要使用 type assertions 使编译器相信您的实现实际上符合调用签名。不太可能自行验证这一点。所以要小心。

Playground link to code