[英]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
时,它有一些限制。
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.我们需要为它获取更具体的类型。
The type for the boolean
values true
and false
will generally be inferred as boolean
instead of their literal values. boolean
值true
和false
的类型通常会被推断为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
那么它不会扩展true
或false
,因此它不会满足 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;
}
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 只记得mySchema
是Record<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.