网站搜索

在 TypeScript 中动态提取类型


我正在尝试根据某些标志动态地将一种类型映射到另一种类型,这可能会导致可选字段。

type Constraint = {
  required: boolean,
  callback: () => any,
}

type Schema = Record<string, Constraint>;

const mySchema: Schema = {
  bar: {
    required: true,
    callback: () => 1
  },
  foo: {
    required: false,
    callback: () => true
  }
}

type MapSchemaToOutput<T extends Schema> = {
  [K in keyof T as T[K]['required'] extends true ? K : never]: ReturnType<T[K]['callback']>
} & {
  [K in keyof T as T[K]['required'] extends false ? K : never]?: ReturnType<T[K]['callback']>
}

type Output = MapSchemaToOutput<typeof mySchema>;

最终目标是让输出等于:

{
  bar: number,
  foo?: boolean
}

我知道我可以手动进行映射,但我想知道这是否可以动态完成。

答案#1

你的方法可以照原样使用,只需做一个修改。

问题在于 : Schema 注解正在“丢弃类型信息”:

const mySchema: Schema = {
   //...
};

通过该注解,TS 只会记住 mySchema 是 Record<string, Constraint>,而不是对象的任何具体结构。


一种修复方法是 as const:

const mySchema = {
    //...
} as const;

这会保留对象中的字面量类型。然而,对于 mySchema 的内容不再有任何约束,并且定义 mySchema 时的任何错误都必须在使用时捕获,而不是在定义时捕获。


一个更好的解决方法是使用辅助函数来引入约束,而不是直接注解类型:

function buildSchema<T extends Schema>(schema: T) { return schema; }

const mySchema = buildSchema({
   //...
});

由于 <T extends Schema> 限制,如果 schema 对象与指定类型不匹配,TS 将像以前一样抛出错误。

但与注释对象的类型不同,这个函数返回的类型与传递给函数的字面对象保持不变:因此不会丢失类型信息。

有了这个改变,其余的类型都按预期工作

答案#2

鉴于适当的 T 类型,你的 MapSchemaToOutput 类型实际上是正确的。然而,当我们将其应用于 typeof mySchema 时,它有一些限制。

记录

Schema 是一个 Record,这意味着从定义上讲,它的键是每一个 string。我们失去了查看实际存在的具体键的能力。

你的地图类型因为 extends 是可以的。但我们不想将类型 Schema 应用于变量 mySchema。我们需要为它获得一个更具体的类型。

布尔类型

boolean 值 true 和 false 的类型通常会被推断为 boolean,而不是它们的字面值。如果 T[K]['required'] 的类型是 boolean,那么它不会扩展 true 或 false,因此不会满足映射的任何条件。

我建议移除可选属性键的 extends 检查,这样所有属性默认都会被包括为可选。在两个地方都包含必需值不是问题,因为它们是用 & 连接的,所以必须存在才能同时满足两个条件。

as const 和 readonly

为了获取 mySchema 的字面 boolean 值,我们需要使用 as const。这会将所有值推断为字面量类型。它还会将类型设为 readonly,映射后的输出也将变为 readonly。我们可以通过在 MapSchemaToOutput 类型的键上添加 -readonly 来移除 readonly。

把这一切放在一起,我们得到:

type MapSchemaToOutput<T extends Schema> = {
 -readonly[K in keyof T as T[K]['required'] extends true ? K : never]: ReturnType<T[K]['callback']>
} & {
 -readonly[K in keyof T]?: ReturnType<T[K]['callback']>
}
type Output = MapSchemaToOutput<typeof mySchema>;

解决为:

type Output = {
    bar: number;
} & {
    bar?: number | undefined;
    foo?: boolean | undefined;
}

TypeScript 游乐场链接