一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第7天,点击查看活动详情。
中等
ReplaceKeys
type ReplaceKeys<U, T extends string, Y> =
U extends U
? {
[P in keyof U]: P extends T ? (P extends keyof Y ? Y[P] : never) : U[P]
}
: U;
-
U将是一个联合类型,我们要对其中的每一个对象类型都进行处理,所以要让其发生分布行为,用U extends U就可以实现 -
具体要处理的是对象中键对应的值类型,所以要遍历每个对象
/*
P extends T:用于确认是否对要当前键进行处理
(P extends keyof Y ? Y[P] : never):因为不能直接使用 Y[P],所以要进行类型推断
*/
{
[P in keyof U]: P extends T ? (P extends keyof Y ? Y[P] : never) : U[P]
}
Remove Index Signature
type RemoveIndexSignature<T> = {
[P in keyof T as (P extends `${infer K}` ? K : never)]: T[P];
}
- 键是可通过
as进行重新映射的,所以这时可以使用模板字符串进行扩展或条件判断 - 而通过
${infer K}可以推断出键的字面量类型,所以能够排除掉索引类型,获取到普通的键
答案参考自解答区,大佬也有详细的解释,并对case进行了扩展。
Percentage Parser
// 检查字符串前缀
type CheckPrefix<T extends string> = T extends '+' | '-' ? T : never;
// 检查字符串中的数值与 %
type CheckSuffix<T extends string> = T extends `${infer R}%` ? [R, '%'] : [T, ''];
type PercentageParser<A extends string> = A extends `${CheckPrefix<infer F>}${infer E}` ? [F, ...CheckSuffix<E>] : ['', ...CheckSuffix<A>];
- 首先使用
CheckPrefix<infer F>将字符串的第一个字符进行处理,从而判断出是不是指定的前缀 - 之后就是根据不同的判断结果组装数组,
CheckSuffix专门用来处理字符串中的数值与百分比符号
Drop Char
type DropChar<S, C> =
S extends `${infer F}${infer E}`
? `${F extends C ? '' : F}${DropChar<E, C>}`
: S;
- 遍历字符串
S,然后对第一个字符F进行处理,字符串E则使用DropChar进行处理
MinusOne
// 数值大小与元组长度相等的映射对象
type DigitToArray = {
"0": [],
"1": [unknown],
"2": [unknown, unknown],
"3": [unknown, unknown, unknown],
"4": [unknown, unknown, unknown, unknown],
"5": [unknown, unknown, unknown, unknown, unknown],
"6": [unknown, unknown, unknown, unknown, unknown, unknown],
"7": [unknown, unknown, unknown, unknown, unknown, unknown, unknown],
"8": [unknown, unknown, unknown, unknown, unknown, unknown, unknown, unknown],
"9": [unknown, unknown, unknown, unknown, unknown, unknown, unknown, unknown, unknown]
};
// N 是一个转换为字符串的数值,根据它创建一个长度为 N 的元组
type CreateArrayByLength<N extends string, R extends unknown[] = []> = N extends `${infer First}${infer Rest}`
? First extends keyof DigitToArray
? CreateArrayByLength<Rest, [...R, ...R, ...R, ...R, ...R, ...R, ...R, ...R, ...R, ...R, ...DigitToArray[First]]>
: never
: R;
// 主类型
type MinusOne<T extends number> = CreateArrayByLength<`${T}`> extends [infer First, ...infer Rest]
? Rest["length"]
: never;
我们根据实际的case进行剖析理解:
1. Expect<Equal<MinusOne<1>, 0>>
// 1. T = 1,代入主类型得到:
type MinusOne = CreateArrayByLength<`${1}`> extends [infer First, ...infer Rest]
? Rest["length"]
: never;
接着看CreateArrayByLength类型:
// 1.1 代入类型中,此时 N = 1,R = [],所以得到:
type CreateArrayByLength = '1' extends `${infer First}${infer Rest}`
? First extends keyof DigitToArray
? CreateArrayByLength<Rest, [...R, ...R, ...R, ...R, ...R, ...R, ...R, ...R, ...R, ...R, ...DigitToArray[First]]>
: never
: R;
// 1.2 '1' extends `${infer First}${infer Rest}` 与 First extends keyof DigitToArray 都是成立的,所以有:
CreateArrayByLength<'', [...R, ...R, ...R, ...R, ...R, ...R, ...R, ...R, ...R, ...R, ...DigitToArray['1']]>
// 相当于
CreateArrayByLength<'', [unknown]>;
// 1.3 此时 N = '',R = [unknown],所以得到:
type CreateArrayByLength = '' extends `${infer First}${infer Rest}`
? First extends keyof DigitToArray
? CreateArrayByLength<Rest, [...R, ...R, ...R, ...R, ...R, ...R, ...R, ...R, ...R, ...R, ...DigitToArray[First]]>
: never
: R;
// 空字符串的判断条件不成立,最后直接返回 R,也就是 [unknown]
[unknown]
根据 1.3 得到的结果,我们代入到主类型中:
type MinusOne = [unknown] extends [infer First, ...infer Rest]
? Rest["length"]
: never;
// 判断条件成立,则有:
type MinusOne = []["length"]
元组类型是允许推断出长度的,所以可以通过length属性获取到长度值,最后结果就是 0。
2. Expect<Equal<MinusOne<55>, 54>>
我这里就直接跳过第一步了,进入到 1.1 步骤:
// 1.1 代入类型中,此时 N = 55,R = [],所以得到:
type CreateArrayByLength = '55' extends `${infer First}${infer Rest}`
? First extends keyof DigitToArray
? CreateArrayByLength<Rest, [...R, ...R, ...R, ...R, ...R, ...R, ...R, ...R, ...R, ...R, ...DigitToArray[First]]>
: never
: R;
// 1.2 两个判断条件都是成立的,所以有:
CreateArrayByLength<'5', [...R, ...R, ...R, ...R, ...R, ...R, ...R, ...R, ...R, ...R, ...DigitToArray['5']]>
// 相当于, 第二个参数的数组长度此时为 5
CreateArrayByLength<'5', [unknown, unknown, unknown, unknown, unknown]>;
// 1.3 根据1.2得到的结果,再次调用CreateArrayByLength
// 此时 N = '5',R = [ 这里面是 5 个unknown],并且两个条件判断都会成立,所以得到:
type CreateArrayByLength = '5' extends `${infer First}${infer Rest}`
? '5' extends keyof DigitToArray
? CreateArrayByLength<'', [...R, ...R, ...R, ...R, ...R, ...R, ...R, ...R, ...R, ...R, ...DigitToArray['5']]>
: never
: R;
// 相当于
type CreateArrayByLength = CreateArrayByLength<'', [<这里有 50 个 unknown>, <这里是 5 个 unknown>]>
// 1.4 空字符串的判断条件不成立,最后直接返回 R,也就是:
[<这里有 50 个 unknown>, <这里是 5 个 unknown>]
根据 1.4 得到的结果,我们代入到主类型中:
type MinusOne = [<这里有 50 个 unknown>, <这里是 5 个 unknown>] extends [infer First, ...infer Rest]
? Rest["length"]
: never;
// 判断条件成立,则有:
type MinusOne = [<这里有 54 个 unknown>]["length"]
最终结果就是 54。
总结:
- 第一步就是先将数值转为字符串,然后遍历每一个字符串,得到长度等于该数值的一个元组
- 每一次将调用
CreateArrayByLength的时候,都会将当前的R的数量 * 10 再加上当前N值获取的元组数量,形成一个累加,再进行传递
- 每一次将调用
- 第二部就是通过条件判断 +
rest参数,从而达到元组长度 - 1 的效果 - 最后根据通过新元组就能获取到对应的长度了
参考自解答区,有些答案非常复杂,这个比较好理解。