【问题标题】:Dynamically extract type in TypeScript在 TypeScript 中动态提取类型
【发布时间】:2021-06-04 08:53:33
【问题描述】:

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

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
}

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

【问题讨论】:

    标签: typescript typescript-typings mapped-types


    【解决方案1】:

    您的方法按原样工作,只需进行一处更改。

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

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

    有了这个注解,TS 只记得mySchemaRecord&lt;string, Constraint&gt;不是对象的任何具体结构


    一个修复是as const:

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

    这会保留对象中的文字类型。但是,mySchema 的内容不再有任何限制,定义 mySchema 的任何错误都必须由用法而不是在定义时捕获。


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

    function buildSchema<T extends Schema>(schema: T) { return schema; }
    
    const mySchema = buildSchema({
       //...
    });
    

    由于&lt;T extends Schema&gt; 约束,如果架构对象与指定类型不匹配,TS 将像以前一样引发错误。

    但与注释对象的类型不同,此函数返回的此类型与传递给函数的字面量对象相比没有变化:因此不会丢失类型信息。

    With this change, the rest of the types work as expected

    【讨论】:

      【解决方案2】:

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

      记录

      Schema 是一个Record,这意味着根据定义,它的键是每个string。我们失去了查看实际存在的特定键的能力。

      由于extends,您的地图类型没问题。但是我们不想将Schema 类型应用于变量mySchema。我们需要为它获取更具体的类型。

      布尔类型

      booleantruefalse 的类型通常会被推断为 boolean 而不是它们的字面值。如果T[K]['required'] 的类型是boolean,那么它不会扩展truefalse,因此它不会满足地图的任何一个条件。

      我建议删除extends 对可选属性键的检查,以便默认情况下所有属性都作为可选属性包含在内。在两个地方都包含所需的值不是问题,因为它们与&amp; 连接,因此它必须存在才能匹配这两个条件。

      as const & readonly

      为了获得mySchema 的文字boolean 值,我们需要使用as const。这将所有值推断为文字。它还使类型为readonly,并且映射的输出也将变为readonly。我们可以通过将-readonly 添加到MapSchemaToOutput 类型的键中来删除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 Playground Link

      【讨论】:

      • 很棒的详细答案,谢谢!使用@Retsam 的答案,因为辅助函数看起来更加优雅。
      猜你喜欢
      • 2022-01-19
      • 2021-01-20
      • 1970-01-01
      • 2021-06-18
      • 2020-04-03
      • 2021-06-12
      • 2022-01-23
      • 2021-10-07
      • 2022-01-17
      相关资源
      最近更新 更多