在 TypeScript 中动态提取类型
Dynamically extract type in 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
}
我知道我可以手动进行映射,有兴趣知道这是否可以动态完成。
您的 MapSchemaToOutput
类型实际上是正确的 T
类型。但是当我们将它应用到 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
。我们可以通过将 -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;
}
您的方法按原样工作,只需进行一处更改。
问题是 : 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>
约束,如果架构对象与指定类型不匹配,TS 将像以前一样引发错误。
但与注释对象的类型不同,此函数返回的此类型与传递给函数的文字对象没有变化:因此不会丢失任何类型信息。
我正在尝试根据某些标志将一种类型动态映射到另一种类型,这可能会产生可选字段。
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
}
我知道我可以手动进行映射,有兴趣知道这是否可以动态完成。
您的 MapSchemaToOutput
类型实际上是正确的 T
类型。但是当我们将它应用到 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
。我们可以通过将 -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;
}
您的方法按原样工作,只需进行一处更改。
问题是 : 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>
约束,如果架构对象与指定类型不匹配,TS 将像以前一样引发错误。
但与注释对象的类型不同,此函数返回的此类型与传递给函数的文字对象没有变化:因此不会丢失任何类型信息。