简体   繁体   English

动态提取 TypeScript 中的类型

[英]Dynamically extract type in TypeScript

I am trying to dynamically map one type to another based on certain flags, which might result in optional fields.我正在尝试根据某些标志动态地将 map 一种类型转换为另一种类型,这可能会导致可选字段。

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

The end goal is to have Output equal:最终目标是让 Output 等于:

{
  bar: number,
  foo?: boolean
}

I know I can do the mapping by hand, interested to know if this can be done dynamically.我知道我可以手动进行映射,有兴趣知道这是否可以动态完成。

Your MapSchemaToOutput type is actually correct given the appropriate T type.给定适当的T类型,您的MapSchemaToOutput类型实际上是正确的。 However it has some limitations when we apply it to typeof mySchema .但是,当我们将其应用于typeof mySchema时,它有一些限制。

Record记录

Schema is a Record which means by definition that its keys are every string . Schema是一个Record ,根据定义,它的键是每个string We lose the ability to see the specific keys which are actually present.我们失去了查看实际存在的特定键的能力。

Your map type is ok because of extends .由于extends ,您的 map 类型没问题。 But we don't want to apply the type Schema to the variable mySchema .但是我们不想将Schema类型应用于变量mySchema We need to get a more specific type for it.我们需要为它获取更具体的类型。

Boolean Types Boolean 类型

The type for the boolean values true and false will generally be inferred as boolean instead of their literal values. booleantruefalse的类型通常会被推断为boolean而不是它们的字面值。 If the type of T[K]['required'] is boolean then that doesn't extend true or false so it won't meet either condition of the map.如果T[K]['required']的类型是boolean那么它不会扩展truefalse ,因此它不会满足 map 的任何一个条件。

I recommend removing the extends check for the optional property keys such that all properties will be included as optional by default.我建议删除可选属性键的extends检查,以便默认情况下所有属性都作为可选属性包含在内。 Including required values in both places is not an issue because they are joined with & so it must be present in order to match both conditions.在两个地方都包含所需的值不是问题,因为它们与&连接,因此它必须存在才能匹配这两个条件。

as const & readonly as const & readonly

In order to get the literal boolean values of mySchema we need to use as const .为了获得mySchema的字面boolean值,我们需要使用as const This infers all values as literals.这将所有值推断为文字。 It also makes the type readonly and the mapped output will become readonly as well.它还使类型为readonly ,映射的 output 也将变为readonly We can remove the readonly by adding -readonly to the keys in the MapSchemaToOutput type.我们可以通过在MapSchemaToOutput类型的键中添加-readonly来删除readonly

Putting that all together, we get:综上所述,我们得到:

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

resolves to:决议:

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

Typescript Playground Link Typescript 游乐场链接

You approach works as-is, with one change.您的方法按原样工作,只需进行一次更改。

The issue is that the : Schema annotation is "throwing away type information":问题是: Schema注释是“丢弃类型信息”:

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

With that annotation, TS only remembers that mySchema is Record<string, Constraint> , not any of the specific structure of the object .使用该注释,TS 只记得mySchemaRecord<string, Constraint>而不是 object 的任何特定结构


One fix is as const :一种修复方法是as const

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

This preserves the literal types within the object.这将保留 object 中的文字类型。 However, there's no longer any constraints on the contents of mySchema , and any errors defining mySchema would have to be caught by the usage, rather than at definition-time.但是,对mySchema的内容不再有任何限制,并且定义mySchema的任何错误都必须被使用捕获,而不是在定义时捕获。


A better fix is to use a helper function to introduce a constraint, without annotating the type directly:更好的解决方法是使用帮助器 function 来引入约束,而不直接注释类型:

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

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

Due to the <T extends Schema> constraint, TS will raise an error, as before, if the schema object doesn't match the specified type.由于<T extends Schema>约束,如果架构 object 与指定的类型不匹配,TS 将像以前一样引发错误。

But unlike annotating the object's type, this type returned by this function is unchanged from the literal object which is passed to the function: so no type information is lost.但与注释对象的类型不同,此 function 返回的此类型与传递给 function 的文字 object 没有变化:因此没有类型信息丢失。

With this change, the rest of the types work as expected 通过此更改,类型的 rest 可以按预期工作

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM