是否可以将较大类型的部分提取为子类型?

Is it possible to extract parts of a larger type into subtypes?

如果你有 3 个子类型将它们组合成一个大的子类型是微不足道的:

type Strings = {
  a: string;
  b: string;
  c: string;
  ...
};
type Numbers = {
  one: number;
  two: number;
  ...
};
type Booleans = {
  True: boolean;
  ...
};
type All = Strings & Numbers & Booleans;

但是如果您从组合类型开始呢?

type All = {
  a: string;
  b: string;
  c: string;
  one: number; 
  two: number;
  True: boolean;
  ...
}

是否可以根据每个键值的类型将All拆分为上述3个子类型?

我想也许这行得通,但行不通

type noNumbers= Exclude<All, { [key: string]: number }>;

TSplayground

不确定是否有更简单或更好的方法,但这行得通。

type PickOut<T, K> = {
    [P in keyof T]: T[P] extends K ? P : never
}[keyof T]

type NumberKeys = PickOut<All, number>
type BooleanKeys = PickOut<All, boolean>
type StringKeys = PickOut<All, string>

type justNumbers = Pick<All, NumberKeys>
type justBooleans = Pick<All, BooleanKeys >
type justStrings = Pick<All, StringKeys >

TSplayground

我们可以自定义类型级别的功能,以实现或省略按值类型或按值类型选择。在两个选项下方:

// picks given values
type PickByValue<T, V, _Keys extends keyof T = {
  [K in keyof T]: T[K] extends V ? K : never
}[keyof T]> = Pick<T, _Keys>

type Numbers = PickByValue<All, number>
type Strings = PickByValue<All, string>

// omits given values
type OmitByValue<T, V, _Keys extends keyof T = {
  [K in keyof T]: T[K] extends V ? K : never
}[keyof T]> = Omit<T, _Keys>

type Bools = OmitByValue<All, number | string>

成功的关键是union类型如何踩never,它是|运算符的中立元素,也就是说它只是被跳过了,对类型没有影响。考虑小证明:

type X = 'a' | never | 'b' // evaluates to just 'a' | 'b'

感谢我们可以使用 never 来跳过一些键。这在这部分是完全可见的:

{
  [K in keyof T]: T[K] extends V ? K : never
}[keyof T]

真正发生的事情是——我们创建映射类型,其值等于键,但前提是值类型是正确的,否则我们使用 never,然后我们通过索引获取这些值 [keyof T]。让我们逐步回顾一下发生了什么:

  • 映射类型使映射类型key-> (key | never)
  • [keyof T] 将所有值类型收集为联合(不考虑 never 元素)
  • 我们现在拥有所有未被 never
  • 跳过的键
  • 我们在实用程序类型 PickOmit
  • 中使用这些键