/**
@date 2021-01-16
@description ts-type-challenge-中等题目第二弹
*/
壹(序)
时间过去三周了,依然是每天做一题,有时候觉得有些题目真的挺简单的,可以一下子就想出来了,我想这是我的成长,继续加油~
贰(Flatten)
第一时间想到的肯定是需要使用递归,然后需要想递归跳出的条件,暂时想不出,继续往下,我们需要对每一项进行判断,判断是否为数组,为数组的话则需要递归,那么怎么对每一项做单独的处理呢?从第一项开始,一项一项的处理,处理第一项的时候剩下的同样递归,所以可以得到:
type Flatten<T> = T extends [infer Start, ...infer Last]
? Start extends any[]
? [...Flatten<Start>, ...Flatten<Last>]
: [Start, ...Flatten<Last>]
: []
现在该思考递归跳出的条件了,不过好像不需要思考了,现在已经成功了,因为如果 T extends [infer Start, ...infer Last]
走的是 false
分支,那么表示 T
此时是个空数组,这就是递归跳出的条件,不过为了严谨,我们需要让传入的参数为 T extends any[]
type Flatten<T extends any[]> = T extends [infer Start, ...infer Last]
? Start extends any[]
? [...Flatten<Start>, ...Flatten<Last>]
: [Start, ...Flatten<Last>]
: []
叁(Append to object)
往对象中增加属性,并不是使用 &
运算符将两个对象链接起来,所以下面是行不通的:
type AppendToObject<T extends object, U extends string, V> = {
[K in keyof T]: T[K]
} & {
[P in U]: V
}
那么如果这样行不通,是否可以换一种思路,关键代码还是
{
[K in keyof T]: T[K]
}
不过我们需要自己定义这个T,所以自定义一个泛型 R
,默认值就是 T
与 { [U]: V }
的结合:
type AppendToObject<T extends object, U extends string, V, R = T & { [ K in U]: V }> = {
[P in keyof R]: R[P]
}
肆(Absolute)
看到这个题的第一反应是,如果传入的参数直接就是一个 string
,那么其实就和之前的一道题 TrimLeft
很类似了,只需要处理 '-', '_', 'n'
三种情况即可,所以自定义一个入参 U
转换为字符的 T
:
type Absolute<T extends number | string | bigint, U = `${T}`> = U extends `${'-' | '_' | 'n'}${infer L}`
? Absolute<L>
: `${T}`
伍(Merge)
Merge 与 Append to object类似,都可以自定义一个类型,然后基于此类型做处理,关键是如何得到自定义的类型;
首先我们需要得到两个对象中属性名相同的值,记得之前有道题是实现 MyExclude,那么我们可以实现一个 Include 来获取相同值:
type Include<T, U> = U extends T ? U : never
那么就能获取到 Merge 的 参数 F 和 S 的相同属性了,然后用 Pick 从 S 中获取到这些属性及其类型,所以我们自定义的第三个参数就变成了:
type Merge<F, S, R = Pick<S, Include<keyof F, keyof S>>> = any
然后再用 &
运算符结合 F
和 S
,再用 Omit
将相同属性排除,就得到了所有 F
和 S
的属性,且其中相同的属性用 S
的属性覆盖,最后得到自定义的 R
,然后基于 R
做处理:
type Merge<F, S, R = Omit<F & S, Include<keyof F, keyof S>> & Pick<S, Include<keyof F, keyof S>>> = {
[K in keyof R]: R[K]
}
这道题结合了之前做过的 Exclude
以及 Pick
以及 Append to object
,首先基于 Exclude
实现一个 Include
(就是Extract),然后基于 Append to object
的实现思想来自定义一个 R
,最后基于 R
做处理,得到结果,总体来说比较复杂,但是这是一个结合学习成果的过程,让我觉得很有意义,所以即使不够优雅,也是一道值得纪念的题目。
陆(CamelCase)
type CamelCase<S> =
S extends `${infer L}${infer R}`
? L extends '-'
? R extends Capitalize<R>
? `${L}${CamelCase<R>}`
: `${CamelCase<Capitalize<R>>}`
: `${L}${CamelCase<R>}`
: S
柒(KebabCase)
type KebabCase<S, F extends boolean = true> = S extends `${infer Start}${infer Last}`
? Start extends Uppercase<Start>
? Start extends '-' | '_'
? `${Start}${KebabCase<Last, false>}`
: F extends true
? `${Lowercase<Start>}${KebabCase<Last, false>}`
: `-${Lowercase<Start>}${KebabCase<Last, false>}`
: `${Lowercase<Start>}${KebabCase<Last, false>}`
: S
捌(Diff)
这道题又让我想起之前的 Merge
和 Append to object
,明白题意之后就知道,我需要自定义一个泛型,然后处理这个泛型即可,主要问题在于如何得到两个对象的 diff
片段,依然是想到之前的 Merge
,然后使用了 Exclude
,将O与O1中O的diff部分取出来,再将O1的diff部分取出来,结合在一起组成 R
,基于R返回diff对象
type Diff<O, O1, R = {
[K in Exclude<keyof O, keyof O1>]: O[K]
} & {
[K in Exclude<keyof O1, keyof O>]: O1[K]
}> = {
[K in keyof R]: R[K]
}
玖(AnyOf)
题目:数组中全部为假值('', 0, false, null, undefined, [], {})则返回false,否则返回true;
遍历数组中每一项,只要有其中一项
是不为假值
的,那么就能提前结束遍历并返回 true
,最后遍历完成也没有返回true则说明全是假值需要返回 false
;
那么关键就在于如何判断某个值是真值还是假值,假值是固定的几个,所以判断某个值是假值更简单,那么可以得到:
type FlaselyValues = '' | 0 | false | null | undefined | [] | { [key: string]: never }
type IsFlasely<T> = T extends FlaselyValues ? true : false
所以最后的结果如下:
type AnyOf<T extends readonly any[]> = IsFlasely<T[number]> extends true ? false : true
不过总感觉这样看起来怪怪的,所以改进了一下:
type FlaselyValues = '' | 0 | false | null | undefined | [] | { [key: string]: never }
type AnyOf<T extends readonly any[]> = T[number] extends FlaselyValues ? false : true
这里注意一个问题:ts中如何判断一个类型是空对象?
一开始我直接用 {}
进行判断,但是一直不对,最后发现:
type Test1 = '' extends {} ? true : false; // true
type Test1 = 0 extends {} ? true : false; // true
这是怎么回事呢?在js中,如果我们声明一个number num = 1
,我们也能使用Number对象
的所有方法,这是js的一种特性,所以ts中其实也一样,所以'' extends Object => true
,而{}
可以看作Object
的一种特殊形式,所以'' extends {} => true
(注意是Object而不是object)
但是ts中的object
就是值非原始值
的类型,在这里用object肯定是不行的,还是得从{}
入手;
空对象,那么就是说明内部没有任何值,所以可以使用never
得到空对象得表示方法:
type NullObject = {
[key: string]: never
}
拾(IsNever)
之前的某道题中出现过,利用泛型判断是否是never
不能直接extends never
,因为类型系统的机制就是如果传入的是never
,那么一定返回never
,所以需要加一层包装:
type IsNever<T> = [T] extends [never] ? true : false
拾壹(ReplaceKeys)
返回的还是U中的所有属性,只是属性值需要做一下处理,根据题意一步一步处理即可:
type ReplaceKeys<U, T, Y> = {
[K in keyof U]: K extends T ? K extends keyof Y ? Y[K] : never : U[K]
}
拾贰(RemoveIndexSignature)
像这种形式的类型
type Foo = {
[key: string]: any;
foo(): void;
}
想要去除[key: string]: any;
的属性,就需要根据此属性自身做处理,这种情况我们通常会使用[K in keyof T]
来获取属性名,而 K 在[key: string]: any;
的情况下,肯定是string
,所以基于此做一个判断即可:
type RemoveIndexSignature<T> = {
[K in keyof T as (string extends K ? never : number extends K ? never : K)]: T[K]
}
拾叁(Percentage Parser)
看起来有点复杂,其实思路很简单,就是一步步的做判断:
type PercentageParser<A extends string> =
A extends ''
? ['', '', '']
: A extends `${infer Start}${infer Middle}%`
? Start extends '+' | '-'
? [Start, Middle, '%']
: ['', `${Start}${Middle}`, '%']
: A extends `${infer Start}${infer Middle}`
? [Start, Middle, '']
: ['', '', '']
拾肆(DropChar)
需要丢掉匹配的值,那么使用 infer 推导出是否还有能够丢掉的值,有的话递归处理,没有直接返回:
type DropChar<S extends string, C extends string> = S extends `${infer Start}${C}${infer Last}` ? DropChar<`${Start}${Last}`, C> : S
拾伍(Replace)
判断T
里面的key
是否 extends U 即可:
type PickByType<T, U> = {
[K in keyof T as T[K] extends U ? K : never]: T[K]
}
拾陆(StartsWith)
利用infer
及模板字符:
type StartsWith<T extends string, U extends string> = T extends `${U}${infer Last}` ? true : false
拾柒(EndsWith)
同上StartsWith:
type EndsWith<T extends string, U extends string> = T extends `${infer Start}${U}` ? true : false
拾捌(PartialByKeys)
这种类似的题之前遇到过好几次,是需要自定义一个类型,然后处理该类型,这里就自定义R
,满足extends K
的值加上可选操作符?
,不满足的值在R
的下一部分处理:
type PartialByKeys<T, K = keyof T, R = {
[key in keyof T as key extends K ? key : never]?: T[key]
} & {
[key in keyof T as key extends K ? never : key]: T[key]
}> = {
[key in keyof R]: R[key]
}
拾玖(RequiredByKeys)
同PartialByKeys
,只是?
变为-?
:
type RequiredByKeys<T, K = keyof T, R = {
[key in keyof T as key extends K ? key : never]-?: T[key]
} & {
[key in keyof T as key extends K ? never : key]: T[key]
}> = {
[key in keyof R]: R[key]
}
贰拾(Mutable)
Readonly
的反转:
type Mutable<T> = {
-readonly [K in keyof T]: T[K]
}
贰拾壹(OmitByType)
在遍历key
时做一下判断即可:
type OmitByType<T, U> = {
[K in keyof T as T[K] extends U ? never : K]: T[K]
}
贰拾贰(ObjectEntries)
需要返回的肯定是一个数组,所以先把需要返回的数组构造好:[K, T[K]
,那么如何使结果是一个联合类型呢,需要遍历T
的所有key
,然后对T做一下Required
处理,因为能在entries
中的,肯定是有值的,最后得到:
type ObjectEntries<T, K = keyof T> = K extends keyof T ? [K, Required<T>[K]] : never
贰拾叁(Shift)
infer推断一下即可:
type Shift<T> = T extends [infer Start, ...infer Last] ? Last : []