更强大的使用TypeScript(3)

197 阅读4分钟
    /**
        @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

然后再用 & 运算符结合 FS,再用 Omit 将相同属性排除,就得到了所有 FS 的属性,且其中相同的属性用 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)

这道题又让我想起之前的 MergeAppend 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 : []