网站搜索

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 的指导的地方:

  1. https://gist.github.com/staltz/368866ea6b8a167fbdac58cddf79c1bf
  2. https://github.com/piotrwitek/utility-types