首页 > 解决方案 > 将一组参数化泛型类型转换为一个泛型类型,该泛型类型通过与原始泛型集分离的参数进行参数化

问题描述

如何covert从下面的示例中定义函数?

const result: BoxType<{
  name: "foo",
  value: FooType
} | {
  name: "bar",
  value: BarType
}> = /* --> */ convert<{
  foo: FooType,
  bar: BarType
}>({
  foo: BoxType<FooType>(),
  bar: BoxType<BarType>()
}) /* <-- */

我可以想出这样的东西

type Input<T> = {[type in keyof T]: BoxType<T[type]>}
type Output<T> = BoxType<{type: keyof T, value: T[keyof T]}>

function convert<T>(input: Input<T>): Output<T> {
  /* ... */
}

Output类型显然无效。这甚至可行吗?:)

标签: typescripttypescript-generics

解决方案


假设我们有一些这样的定义:

interface BoxType<T> { box: T }
interface FooType { foo: string }
interface BarType { bar: string }
declare function BoxType<T>(): BoxType<T>;

我们可以给convert()函数以下调用签名:

declare function convert<T extends object>(
  input: { [K in keyof T]: BoxType<T[K]> }
): BoxType<{ [K in keyof T]-?: { name: K, value: T[K] } }[keyof T]>;

我认为这符合你想要的方式:

const result = convert({
  foo: BoxType<FooType>(),
  bar: BoxType<BarType>()
})

/*
const result: BoxType<{
    name: "foo";
    value: FooType;
} | {
    name: "bar";
    value: BarType;
}>
*/

convert()函数是通用的T,一个对象类型从不直接用作输入或输出,但看起来像input具有“未装箱”属性的类型。我们可以将input类型定义T为直接映射类型{[K in keyof T]: BoxType<T[K]> },我们将每个属性“装箱”。由于input映射的类型超过keyof T,这被认为是“同态”映射类型,编译器可以从推断 。到目前为止,这与您所拥有的相同。Tinput


对于函数的返回类型,我们希望获取每个属性键并生成 的并K集。在您的代码中,您在映射之前先使用联合。但是该类型允许来自一个键和另一个键的各种不良值。为了使它们分开,我们需要遍历 中的每个键,一个简单的方法是使用另一种映射类型,例如.keyof T{ name: K, value: T[K] }{name: keyof T, value: T[keyof T]}namevalueKkeyof T{[K in keyof T]: {name: K, value: T[K]}}

当然,这种类型看起来像{foo: {name: "foo", value: FooType}, bar: {name: "bar", value: BarType}}......属性值类型是我们想要获得并集的类型,我们根本不需要键。幸运的是,在这里我们可以对它进行索引keyof T以产生属性值类型的联合。因此 。{[K in keyof T]: {name: K, value: T[K]}}[keyof T]

最后我们将其包装在BoxType:中。全部做完!等等,哎呀,它与上述输出类型之间的唯一区别是我们通过确保它们不是可选的来修改映射类型的属性:如果您的输入类型恰好具有可选属性, 这可以防止讨厌的值潜入。现在我们完成了。BoxType<{ [K in keyof T]: { name: K, value: T[K] }}[keyof T]>BoxType<{ [K in keyof T]-?: { name: K, value: T[K] } }[keyof T]>undefinedT

Playground 代码链接


推荐阅读