如何让TypeScript类型中的特定嵌套属性变为必填?
实现支持嵌套属性的PartialExcept类型助手
你可以通过定义一个支持嵌套路径指定的类型工具NestedPartialExcept来统一处理多层级的必填属性设置,替代手动嵌套PartialExcept和Required的繁琐写法。以下是完整实现和用法示例:
第一步:定义辅助类型(处理嵌套路径与递归转换)
首先实现几个基础辅助类型,用来解析嵌套属性路径并递归修改类型:
// 提取所有可能的嵌套属性路径(支持数组) type NestedKeys<T> = T extends Array<infer U> ? `${number}.${NestedKeys<U>}` | NestedKeys<U> : T extends object ? { [K in keyof T]: K extends string ? `${K}.${NestedKeys<T[K]>}` | K : never }[keyof T] : never; // 递归将指定路径的属性设为必填,其余保持Partial type NestedRequired<T, K extends string> = T extends Array<infer U> ? Array<NestedRequired<U, K extends `${number}.${infer Rest}` ? Rest : K>> : T extends object ? { [P in keyof T]: P extends K ? Required<T[P]> : K extends `${P}.${infer Rest}` ? NestedRequired<T[P], Rest> : Partial<T[P]>; } & Required<Pick<T, Extract<keyof T, K>>> : T; // 最终的NestedPartialExcept类型:指定嵌套路径为必填,其余全为Partial type NestedPartialExcept<T, K extends NestedKeys<T>> = NestedRequired<Partial<T>, K>;
第二步:使用示例
用你提供的业务类型来测试:
type Team = { name?: string description?: string goals?: Goal[] metadata?: Metadata } type Metadata = { time?: string period?: number shots?: number } type Goal = { scorer?: string number?: string } // 使用NestedPartialExcept统一指定所有嵌套必填属性 type Game = NestedPartialExcept<{ home: Team }, "home.name" | "home.description" | "home.goals[].scorer" | "home.goals[].number" | "home.metadata.time" | "home.metadata.period" >;
这样定义的Game类型和你原来手动拼接的类型完全等价,但写法更统一简洁,所有必填属性都集中在一个联合类型中指定。
类型说明
NestedKeys<T>:自动生成类型T的所有嵌套属性路径,比如对于{ home: Team },会生成"home"、"home.name"、"home.goals[].scorer"等路径。NestedRequired<T, K>:递归遍历类型T,将所有匹配路径K的属性设为Required,其余属性保持Partial。NestedPartialExcept<T, K>:基于Partial<T>进行转换,确保未指定的属性都是可选的,仅指定路径的属性为必填。
内容的提问来源于stack exchange,提问作者Amrit Kahlon
相关产品推荐
相关产品推荐

