Ts 类型体操专项训练

242 阅读4分钟

本文对 11 个Ts 类型体操进行专项训练,喜欢的朋友可以收藏起来今后翻看。

1. 实现一个通用的 RequiredByKeys

实现一个通用的 RequiredByKeys<T, K>,它接收两个类型参数 T 和 K。

K 指定应设为必选的 T 的属性集。当没有提供 K 时,它就和普通的 Required<T> 一样使所有的属性成为必选的。

interface User {
  name?: string;
  age?: number;
  address?: string;
}
type UserRequiredName = RequiredByKeys<User, "name">; // { name: string; age?: number; address?: string }

自写答案

type RequiredByKeys<T extends {}, K extends keyof T, U ={
[P in keyof T as P extends K ? P : never]-?: T[P];
} & {
[P in keyof T as P extends K ? never : P]: T[P];
}> = {
[A in keyof U]: U[A]
}

上面的答案比下面这个好一些:

  type RequiredByKeys<T extends {}, K extends keyof T> = {
    [P in keyof T as P extends K ? P : never]-?: T[P];
  } & {
    [P in keyof T as P extends K ? never : P]: T[P];
  }

这道题告诉我们在等号左边的泛型内也可以进行计算。

答案

type RequiredByKeys<
    T extends object,
    K extends keyof T | string = keyof T,
    H = {
        [P in K extends keyof T ? K : never]: Exclude<T[P], null | undefined>;
    } & { [P in Exclude<keyof T, K>]?: T[P] }
> = { [P in keyof H]: H[P] };

2. 实现一个通用的 PartialByKeys

实现一个通用的PartialByKeys<T, K>,它接收两个类型参数 T 和 K。

K 指定应设置为可选的 T 的属性集。当没有提供 K 时,它就和普通的Partial<T>一样使所有属性都是可选的。

例如:

interface User {
  name: string;
  age: number;
  address: string;
}
type UserPartialName = PartialByKeys<User, "name">; // { name?:string; age:number; address:string }

自写答案

type PartialByKeys2<T extends {}, K=unknown, U = {
    [P in keyof T as P extends K ? P : never]?: T[P]
} & {
    [P in keyof T as P extends K ? never : P]: T[P]
}> =  K extends unknown ? {
    [P in keyof U]: U[P]
} : {
    [P in keyof T]?: T[P]
}

给泛型默认值,一般是 unknown 就可以规避反省数目对不上的报错。

答案

type PartialByKeys<
    T,
    K extends keyof T | string = keyof T,
    H = { [P in Exclude<keyof T, K>]: T[P] } & {
        [P in K extends keyof T ? K : never]?: T[P];
    }
> = { [K in keyof H as [H[K]] extends [never] ? never : K]: H[K] };

3. 实现 EndsWith

实现EndsWith<T, U>,接收两个 string 类型参数,然后判断 T 是否以 U 结尾,根据结果返回 true 或 false

例如:

type a = EndsWith<"abc", "bc">; // expected to be false
type b = EndsWith<"abc", "abc">; // expected to be true
type c = EndsWith<"abc", "d">; // expected to be false

自写答案

type EndsWith<
  A extends string,
  B extends string
> = A extends `${infer F}${infer R}`
  ? B extends A
    ? true
    : EndsWith<R, B>
  : false;

答案

type EndsWith<T extends string, U extends string> = T extends `${any}${U}`
    ? true
    : false;

模板字符串中并不是只能一个一个的比对的,使用 ${any} 可以一次越过好几个。

4. 实现 StartsWith

实现StartsWith<T, U>,接收两个 string 类型参数,然后判断 T 是否以 U 开头,根据结果返回 true 或 false

例如:

type a = StartsWith<"abc", "ac">; // expected to be false
type b = StartsWith<"abc", "ab">; // expected to be true
type c = StartsWith<"abc", "abcd">; // expected to be false

自写答案

type StartsWith<A extends string, B extends string> = A extends `${B}${any}` ? true : false;

5. 实现 PickByType

找出 T 中类型为 U 的属性

示例:

type OnlyBoolean = PickByType<
  {
    name: string;
    count: number;
    isReadonly: boolean;
    isEnable: boolean;
  },
  boolean
>; // { isReadonly: boolean; isEnable: boolean; }

自写答案

type PickByType<T extends {}, K = unknown> = {
  [P in keyof T as T[P] extends K ? P : never]: T[P]
} 

6. 实现 RemoveIndexSignature

实现 RemoveIndexSignature<T>, 将索引字段从对象中排除掉.

示例:

type Foo = {
  [key: string]: any;
  foo(): void;
};
type A = RemoveIndexSignature<Foo>; // expected { foo(): void }

答案

type RemoveIndexSignature<T extends object> = {
  [P in keyof T as string extends P
      ? never
      : number extends P
      ? never
      : symbol extends P
      ? never
      : P]: T[P];
};

写不出来,但是索引序列指的并不是 'name' 这种,上面的方法是专门用来处理索引序列的,背下即可。

7. 实现 MinusOne

给定一个正整数作为类型的参数,要求返回的类型是该数字减 1。

例如:

type Zero = MinusOne<1>; // 0
type FiftyFour = MinusOne<55>; // 54

自写答案

type BuildArr<T extends number, K extends any[] = []> = K["length"] extends T ? K : BuildArr<T, [...K, unknown]>;;

type MinusOne<T extends number> = BuildArr<T> extends [infer F, ...infer R] ? R["length"] : 0;

8. 实现 DropChar

从字符串中剔除指定字符。

例如:

type Butterfly = DropChar<" b u t t e r f l y ! ", " ">; // 'butterfly!'

自写答案

type DropChar<A extends string, B extends string> = A extends `${B}${infer R}` ? (
  DropChar<R, B>
) : (
  A extends `${infer F}${infer R2}` ? `${F}${DropChar<R2, B>}` : A
);

答案

type DropChar<
    S extends string,
    C extends string
> = S extends `${infer L}${infer R}`
    ? `${C extends L ? '' : L}${DropChar<R, C>}`
    : S;

这个答案没有我写的好,我的可以是多个字符。

9. 实现类型 PercentageParser

实现类型 PercentageParser。根据规则 /^(\+|\-)?(\d*)?(\%)?$/ 匹配类型 T。

匹配的结果由三部分组成,分别是:[正负号, 数字, 单位],如果没有匹配,则默认是空字符串。

例如:

type PString1 = "";
type PString2 = "+85%";
type PString3 = "-85%";
type PString4 = "85%";
type PString5 = "85";
type R1 = PercentageParser<PString1>; // expected ['', '', '']
type R2 = PercentageParser<PString2>; // expected ["+", "85", "%"]
type R3 = PercentageParser<PString3>; // expected ["-", "85", "%"]
type R4 = PercentageParser<PString4>; // expected ["", "85", "%"]
type R5 = PercentageParser<PString5>; // expected ["", "85", ""]

自写答案

type PercentageParser<T extends string, Re extends string[] = []> = Re["length"] extends 0 ? (T extends `${'+'}${infer R}` ? PercentageParser<R, ['+']> : (
  T extends `${'-'}${infer R}` ? PercentageParser<R, ['-']> : PercentageParser<T, ['']>
)):(
  T extends `${infer R}${'%'}` ? [...Re, R, '%'] : (
    T extends "" ? [...Re, "", ""] : [...Re, T, ""]
  )
)

答案

type PercentageParser<
    A extends string,
    Sign extends '+' | '-' | '' = '',
    Mark extends '' | '%' = ''
> = A extends `${infer L extends '+' | '-'}${infer R}`
    ? PercentageParser<R, L>
    : A extends `${infer L}%`
    ? PercentageParser<L, Sign, '%'>
    : [Sign, A, Mark];

如果能像答案那样将 + - 合起来处理就会节省很多代码。

10. 实现一个 IsUnion 类型

实现一个 IsUnion 类型, 接受输入类型 T, 并返回 T 是否为联合类型.

例如:

type case1 = IsUnion<string>; // false
type case2 = IsUnion<string | number>; // true
type case3 = IsUnion<[string | number]>; // false

自写答案

type IsUnion<T, K = T> = K extends K ? (
  Exclude<T, K> extends never ? false: true
) : never

这里我们使用了让联合类型缩减的技巧。

11. 实现一个 IsNever 类型

实现一个以 T 作为泛型参数的 IsNever类型. 如果 T 是never, 返回 true, 否则返回 false.

示例:

type A = IsNever<never>; // expected to be true
type B = IsNever<undefined>; // expected to be false
type C = IsNever<null>; // expected to be false
type D = IsNever<[]>; // expected to be false
type E = IsNever<number>; // expected to be false

自写答案

type IsNever<T> = (T extends never ? true : false) extends 1 ? true : false; // 正常值不是 true 就是 false 所以正常值第二次判断之后返回 false

  1. never | A = A
  2. never extends any = true
  3. extends 计算有 3 种情况 true false never
  4. A extends never 除非 A 是 never 则均为假(不考虑 any)

答案

type IsNever<T extends unknown> = [T] extends [never] ? true : false;

只要我们将其装入数组中,使 never 成为其元素就可以避免上述问题了。