携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第5天,点击查看活动详情
中等
Combination
type Combination<T extends string[], All = T[number], Item = All> =
Item extends (infer Item extends string)
? Item | `${Item} ${Combination<[], Exclude<All, Item>>}`
: never
/*
expected to be `
"foo" | "bar" | "baz" | "foo bar" | "foo bar baz" |
"foo baz" | "foo baz bar" | "bar foo" | "bar foo baz" | "bar baz" |
"bar baz foo" | "baz foo" | "baz foo bar" | "baz bar" | "baz bar foo"
`
*/
type Keys = Combination<['foo', 'bar', 'baz']>
答案参考自解答区
infer Item extends string是一个整体,用于推断Item的类型,并限制类型为string。假如不这么写,就得这样:
type Combination<T extends string[], All = T[number], Item = All> =
Item extends Item
// 需要转换类型
? Item | `${Item & string} ${Combination<[], Exclude<All, Item>> & string}`
: never
所以说,就是使用infer将Item的类型给推断出来了。
下面通过一个 case 进行剖析,了解其运行原理。
1. 代入Combination<['foo', 'bar', 'baz']>
type Combination<['foo', 'bar', 'baz'], All = 'foo' | 'bar' | 'baz', Item = 'foo' | 'bar' | 'baz'> =
Item extends (infer Item extends string)
? Item | `${Item} ${Combination<[], Exclude<All, Item>>}`
: never
Item的值为联合类型,那么用在添加类型中时,会发生分布行为,就会对Item中每个值都进行处理。
2. 代入第一个值'foo'
type Combination<['foo', 'bar', 'baz'], All = 'foo' | 'bar' | 'baz', Item = 'foo' | 'bar' | 'baz'> =
'foo' extends 'foo' | 'bar' | 'baz'
// Exclude 的结果为 'bar' | 'baz'
? 'foo' | `foo ${Combination<[], Exclude<'foo' | 'bar' | 'baz', 'foo'>>}`
: never
执行后结果为'foo' | `foo ${Combination<...>}`。
3. 根据上一步的结果,继续代入Combination。
由于Item还是联合类型,所以这里又发生了分布行为,这里代入第一个值'bar':
type Combination<[], All = 'bar' | 'baz', Item = 'bar' | 'baz'> =
'bar' extends 'bar' | 'baz'
// Exclude 的结果为 'baz'
? 'bar' | `bar ${Combination<[], Exclude<'bar' | 'baz', 'bar'>>}`
: never
执行后结果为'foo' | `foo ${'bar' | Combination<...>}`。
4. 根据上一步的结果,继续代入Combination:
type Combination<[], All = 'baz', Item = 'baz'> =
'baz' extends 'baz'
// Exclude 的结果为 never
? 'baz' | `baz ${Combination<[], Exclude<'baz', 'baz'>>}`
: never
到这里就结束了,执行后结果为'foo' | `foo ${'bar' | `bar ${'baz'}`}`。
由于模板字符串的特性,也就变成了:
'foo' | 'foo bar' | 'foo bar baz'
// 要是把 foo 后面的结果完整代入:
'foo' | 'foo bar' | 'foo bar baz' | 'foo baz' | 'foo baz bar'
数组中每个元素都能组成 5 个类型,最终结果就是 15 个类型,跟最上面调用示例中展示的一样。
Subsequence
type Medium<T, U, R extends any[] = [[U]]> =
T extends [infer F, ...infer E]
? Medium<E, U, [...R, [U, F], [U, ...T]]>
: R;
type Subsequence<T extends any[], R extends any[] = [[]]> =
T extends [infer F, ...infer E]
? Subsequence<E, [...R, ...Medium<E, F>]>
: R[number];
// 示例:
type A = Subsequence<[1, 2]> // [] | [1] | [2] | [1, 2]
画图解释:
在解答区看到一个优雅简洁的解答,没想到联合类型还能这么用:
type Subsequence<T extends any[], Prefix extends any[] = []> =
T extends [infer F, ...infer R]
? Subsequence<R, Prefix | [...Prefix, F]>
: Prefix
主要关注下 Prefix | [...Prefix, F],来看个例子就明白了:
type A<Prefix extends any[] = [] | [1]> = [...Prefix, 2];
// 代入一下,得到结果:[2] | [1, 2]
type A = [...([] | [1]), 2];
由于Prefix是个联合类型,所以拆成两步:
[...[], 2]得到[2][...[1], 2]得到[1, 2]
因为是联合类型,所以会有两个结果,最后也就得到了[2] | [1, 2]。