TypeScript类型体操挑战(十七)

1,194 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第6天,点击查看活动详情

中等

AllCombinations

挑战要求

在线示例

// 将字符串中的每个值使用 | 组合成联合类型
type String2Union<S extends string> =
  S extends `${infer C}${infer R}`
  ? C | String2Union<R>
  : never;

type AllCombinations<
  S extends string,
  U extends string = String2Union<S>,
  > = [U] extends [never]
  ? ''
  : '' | { [K in U]: `${K}${AllCombinations<never, Exclude<U, K>>}` }[U];

/*
  使用示例:
  '' | 'A' | 'B' | 'C' | 'AB' | 'AC' | 'BA' | 'BC' | 'CA' 
  | 'CB' | 'ABC' | 'ACB' | 'BAC' | 'BCA' | 'CAB' | 'CBA'
*/
type AllCombinations_ABC = AllCombinations<'ABC'>;

答案参考自解答区👍最多的


通过 case 来分析类型的执行过程,进行理解:

Expect<
  Equal<
    AllCombinations<'ABC'>, 
    '' | 'A' | 'B' | 'C' | 'AB' | 'AC' | 'BA' | 'BC' | 'CA' | 'CB' |
    'ABC' | 'ACB' | 'BAC' | 'BCA' | 'CAB' | 'CBA'
  >

1. 代入到类型中 :

type AllCombinations<'ABC', 'A' | 'B' | 'C'> = 
  ['A' | 'B' | 'C'] extends [never]		// 条件不成立
  ? ''
  // 走到这
  : '' |
    { 
      [K in 'A' | 'B' | 'C']: `${K}${AllCombinations<never, Exclude<U, K>>}`
    }['A' | 'B' | 'C'];

2.'A'为例,看看该键对应的值:

// 代入到类型当中:
{
  A: `A${AllCombinations<never, 'B' | 'C'>}`
}

2.1 分析AllCombinations<never, 'B' | 'C'>

type AllCombinations<never, 'B' | 'C'> = 
  ['B' | 'C'] extends [never]		// 条件不成立
  ? ''
  // 走到这
  : '' |
    { 
      [K in 'B' | 'C']: `${K}${AllCombinations<never, Exclude<U, K>>}`
    }['B' | 'C'];

3. 这次以'B'为例,则其对应的值为:

// 代入到类型当中:
{
  B: `B${AllCombinations<never, 'C'>}`
}

3.1 代入类型AllCombinations<never, 'C'>

type AllCombinations<never, 'C'> = 
  ['C'] extends [never]		// 条件不成立
  ? ''
  // 走到这,就到底了
  : '' |
    { 
      [K in 'C']: `${K}${AllCombinations<never, nerver>}`
    }['C'];

// 则结果为:
'' | {
  // 由于 AllCombinations 一开始对 nerver 类型进行了处理
  // 所以返回结果为 ''
  C: 'C'
}['C']

// 最终结果:
'' | 'C'

根据 3.1 的最终结果,回到 2.1则有:

// 在字符串模板中,A 与联合类型进行拼接
// 则 A 会与联合类型中的每个类型进行拼接
{
  B: `B${'' | 'C'}`
}
// B 的最终结果:
{
  B: 'B' | 'BC'
}


// C 的处理逻辑跟 B 是一样的
'' | {
  B: 'B' | 'BC',
  C: 'C' | 'CB'
}['B' | 'C']
// 所以最终结果为:
'' | 'B' | 'BC' | 'C' | 'CB'

根据上面的结果,再回到第 2 步:

// A 对应的值处理完毕:
{ 
  A: `A${'' | 'B' | 'BC' | 'C' | 'CB'}`
}
// 根据字符串模板的特性,A 的最终结果为:
{ 
  A: 'A' | 'AB' | 'ABC' | 'AC' | 'ACB'
}


// 最终结果:
'' |
{ 
  A: 'A' | 'AB' | 'ABC' | 'AC' | 'ACB',
  // 下面这些跟 A 的处理逻辑一样
  B: 'B' | 'BA' | 'BAC' | 'BC' | 'BCA',
  C: 'C' | 'CA' | 'CAB' | 'CB' | 'CBA',
}['A' | 'B' | 'C']

// 最终结果:
'' | 
'A' | 'AB' | 'ABC' | 'AC' | 'ACB'
'B' | 'BA' | 'BAC' | 'BC' | 'BCA'
'C' | 'CA' | 'CAB' | 'CB' | 'CBA'

总结

  • 首先将字符串S变为一个联合类型,通过 in 遍历,对每个字符进行处理
  • 主要是利用模板字符串的特性,来产生每个可能性的字符串拼接组合

Greater Than(大于)

挑战要求

在线示例

// 创建一个长度为 T 的元组
type Number2Tuple<T, R extends number[] = []> = 
  R['length'] extends T 
  ? R 
  : Number2Tuple<T, [...R, 0]>;

type GreaterThan<T extends number, U extends number, Arr = Number2Tuple<T>> = 
  Arr extends [number, ...infer E]
  ? 
    E['length'] extends U 
    ? true 
    : GreaterThan<T, U, E>
  : false;


// 使用示例
GreaterThan<2, 1> // should be true
GreaterThan<10, 100> //should be false

Number2Tuple<T>用于创建一个长度为T的元组:

// 相当于 type A = [0, 0, 0]
type A = Number2Tuple<3>

执行流程如下: