因为对象不是不可变的,所以您丢失了实际的"值",所以类型脚本(例如)推断kind
是string
,而不是字符串类型"string"
、"number"
或"boolean"
.如果将as const
加到对象文字like this的末尾:
const buttonProps = {
// ...
} as const;
...您告诉TypeScrip对象是不变的,它将推断字符串文字类型.然后我们就可以着手建造mapped type,从buttonProps
推断出你想要的类型:
// A property type in button props
type PropertyEntry = {
kind: "string" | "number" | "boolean";
empty?: boolean;
};
// The type for a property (ignoring `empty`, we'll handle that separately)
type PropertyValueType<T extends PropertyEntry> =
T extends {kind: "string"}
? string
: T extends {kind: "number"}
? number
: T extends {kind: "boolean"}
? boolean
: never;
// The mapped type that accepts the type of an object using `PropertyEntry`
// elements and provides the type that object describes
type PropertiesType<ObjectType extends Record<string, PropertyEntry>> =
// The non-optional ones (no `empty` or `empty: false`):
& {
-readonly [Key in keyof ObjectType as ObjectType[Key] extends {empty: true} ? never : Key]: PropertyValueType<ObjectType[Key]>;
}
// The optional ones (`empty: true`):
& {
-readonly [Key in keyof ObjectType as ObjectType[Key] extends {empty: true} ? Key : never]?: PropertyValueType<ObjectType[Key]>;
};
在这里,我使用了各种mapping modifiers,并将可选属性(empty: true
)与非可选属性分开处理(这样我就可以将可选修饰符?
应用于可选属性).
将其与buttonProps
一起使用:
// Testing with `buttonProps` from the question
const buttonProps = {
name: {kind:"string", empty:false},
height: {kind:"number", min:20, max:50},
width: {kind:"number", min:80},
dark: {kind:"boolean"},
optionalBool: {kind: "boolean", empty: true} // Added this for testing
} as const;
type ButtonProps = PropertiesType<typeof buttonProps>;
// ^? type ButtonProps = { name: string, height: number, width: number, dark: boolean} & { optionalBool?: boolean | undefined }
// Test the type
const x: ButtonProps = {
dark: true,
name: "x",
height: 0,
width: 0,
// Optional prop not required
};
Playground link个
我确信有很多地方需要调整(如果可以更简洁地完成,我也不会感到惊讶),但这是主要的 idea .