如何定义TypeScript类型:仅允许单个属性为undefined
实现恰好一个属性为undefined的类型:
OneUndefined<T> 要实现这个需求,咱们可以用TypeScript的映射类型和联合特性,遍历目标类型的每个属性,为每个属性生成该属性可选为undefined,其余属性保留原类型的子类型,最后把所有子类型联合起来:
type OneUndefined<T> = { [K in keyof T]: Omit<T, K> & { [P in K]?: undefined } }[keyof T];
验证示例
拿你给出的MyType测试:
type MyType = { a: number; b: number; c: number; };
OneUndefined<MyType>会被TypeScript解析为:
| { a?: undefined; b: number; c: number } | { a: number; b?: undefined; c: number } | { a: number; b: number; c?: undefined }
完全匹配你的预期。
逆类型:恰好一个属性已定义的
OneDefined<T> 和OneUndefined<T>逻辑反过来,咱们要生成恰好一个属性保留原类型,其余属性可选为undefined的联合类型:
type OneDefined<T> = { [K in keyof T]: Pick<T, K> & { [P in Exclude<keyof T, K>]?: undefined } }[keyof T];
验证示例
OneDefined<MyType>的结果是:
| { a: number; b?: undefined; c?: undefined } | { a?: undefined; b: number; c?: undefined } | { a?: undefined; b?: undefined; c: number }
通用类型:恰好n个属性已定义的
NDefined<T, n> 要支持任意数量n的通用类型,核心是先生成目标类型所有长度为n的属性键组合,再为每个组合生成对应子类型并联合。
第一步:实现生成指定长度的属性键组合
先写几个辅助工具类型,用来生成所有长度为n的属性键元组:
// 把联合类型转成元组 type UnionToTuple<T> = ( (T extends any ? (t: T) => T : never) extends infer U ? (U extends any ? (u: U) => any : never) extends (v: infer V) => any ? [...UnionToTuple<Exclude<T, V>>, V] : [] : [] ); // 从元组里生成所有长度为N的组合 type Combinations<T extends any[], N extends number, Result extends any[] = []> = N extends 0 ? [Result] : T extends [infer First, ...infer Rest] ? [...Combinations<Rest, N, [...Result, First]>, ...Combinations<Rest, N, Result>] : []; // 获取目标类型所有长度为N的属性键元组 type PickN<T, N extends number> = Combinations<UnionToTuple<keyof T>, N>[number];
第二步:实现NDefined<T, n>
基于上面的属性键组合,生成每个组合对应的选中n个属性保留原类型,其余可选为undefined的子类型,最后联合所有子类型:
type NDefined<T, N extends number> = { [K in PickN<T, N>]: Pick<T, K[number]> & { [P in Exclude<keyof T, K[number]>]?: undefined } }[PickN<T, N>];
验证示例
NDefined<MyType, 2>的结果是:
| { a: number; b: number; c?: undefined } | { a: number; c: number; b?: undefined } | { b: number; c: number; a?: undefined }
对应的NUndefined<T, n>(恰好n个属性为undefined)
如果需要恰好n个属性为undefined,只需调整逻辑为选中n个属性设为可选undefined,其余保留原类型:
type NUndefined<T, N extends number> = { [K in PickN<T, N>]: Omit<T, K[number]> & { [P in K[number]]?: undefined } }[PickN<T, N>];
比如NUndefined<MyType, 1>就完全等价于OneUndefined<MyType>。
内容的提问来源于stack exchange,提问作者Ted
相关产品推荐
相关产品推荐

