Typescript 如何选择和组合嵌套类型作为根类型
我有单一类型,我想重用此类型来连接数据:
interface MetaData {
user: {
id: string;
fullName: string;
},
stats: {
following: number;
},
another: {
any: any;
}
}
我想通过键组合一些嵌套类型
(例如:type Profile=PickAllNestedKey<MetaData, 'user' | 'stats'>
我必须试试这段代码,但它只能选择单个键
type NestedPartial<T, K extends keyof T> = {
[P in keyof T[K]]: T[K][P];
};
type Profile = NestedPartial<Metadata, 'user'>;
期望的类型如:
interface Profile {
id: string;
fullName: string;
following: number;
}
答案#1
根据你的示例,看起来期望的行为是选择 MetaData 的顶层属性,然后“扁平化”结果。
你当前尝试的问题在于 keyof T[K]。如果 K 是一个联合类型,例如 'user' | 'stats',那么 T[K] 就是两个对象的联合类型 { id: string; fullName: string; } | { following: number; }。这个联合类型上没有共同的键。因此不存在可以分配给 P in keyof T[K] 的 P。
如果我们只是Pick我们想要的属性并按键索引它们,那么我们得到的类型几乎是想要的类型,只是它是一个联合类型,而我们想要的是交叉类型。Pick<T, K>[K]解析为:
{
id: string;
fullName: string;
} | {
following: number;
}
我们可以利用 @jcalz 的 UnionToIntersection<T> 工具类型来解决这个问题。
type UnionToIntersection<U> =
(U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never
type NestedPartial<T, K extends keyof T> = UnionToIntersection<Pick<T, K>[K]>;
type Profile = NestedPartial<MetaData, 'user' | 'stats'>;
解析为:
type Profile = {
id: string;
fullName: string;
} & {
following: number;
}
TypeScript 游乐场链接
答案#2
我会从两个不同的角度来处理这件事。
第一个是质疑类型的结构。为什么嵌套类型没有明确定义?也许类型结构可以改进?我的意思是这样的:
interface User {
id: string;
fullName: string;
}
interface Stats {
following: number;
}
interface Another {
any: any;
}
interface MetaData {
user: User,
stats: Stats,
another: Another
}
type Profile = User & Stats
定义更小的、原子级的类型,每次针对一个实体,能够大大提高可读性和代码维护性。这样你就可以自由地在任何合适的地方使用这些较小的类型,而不需要从大类型中创建它们。换句话说,这将方法从自上而下的方式(先创建大类型,然后使用 Pick、Omit 等来构建较小的类型)切换为自下而上的方式(定义单个类型,然后将其用作构建更复杂类型的基础)。
话虽如此,我们并不总是能够控制我们正在处理的类型。因此,这里有几个可以找到关于如何处理嵌套 Pick 的指导的地方:
- https://gist.github.com/staltz/368866ea6b8a167fbdac58cddf79c1bf
- https://github.com/piotrwitek/utility-types
