【Typescript 系列】类型体操之中等篇题型(第二十二节)解读

87 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第11天,点击查看活动详情

1. 引言

接着上一节中,接下来我们继续Ts中等篇的题型练习 https://github.com/type-challenges/type-challenges/blob/main/README.zh-CN.md 提供的TypeScript 类型体操姿势合集题型,目的是为了让大家更好的了解TS的类型系统,编写自己的类型工具,或者单纯的享受挑战的乐趣!

2. 题型

  1. Fill:Fill,一个常见的JavaScript函数,现在让我们用类型来实现它。Fill<T, N, Start? , End?>,如您所见,Fill接受四种类型的参数,其中T和N是必需参数,Start和End是可选参数。参数配置要求如下:“T”必须是元组,“N”可以是任意类型的值,“开始”和“结束”必须是大于等于0的整数。
type exp = Fill<[1, 2, 3], 0> // expected to be [0, 0, 0]

补充:为了模拟真实的功能,测试中可能会包含一些边界条件,希望大家能喜欢:)

思路:    该题主要解题思路为递归处理;首先在方法在定义两个参数,一个Count负责记录检测对象T是否检测完毕,检测长度是否为到达元祖检测End要求结束节点;另一个参数Flag记录检测目标是否处在要求检测的起始点Start; 第一步,开始判断当记录对象Count长度为End结束节点,说明检测完毕,直接返回对象; 第二步,结构目标检测对象T,如果T对象不满足元祖类型结果,则直接抛出目标T; 第三步,需要开始执行改造操作,也是该题最重要的部分,通过判断Flag是否为处在目标起始点值Start,如果不是则通过合并检测结构值R与合并除R以外的元祖值递归执行Fill方法,在方法中Count增长一个长度单位任意值,表示内部元素完成一次操作,接着递归操作其他;如果是则执行与否规则一样的操作只是Flag参数需要开始传递为true,此操作代表当前已经处在开始改造的Start起始位置,往后只有Count记录长度为End结束位代表改造结束,中间这一段过程都需要在执行递归改造中传递类型Flag值为true,告知递归方法检测元素需要改造替换元素内容为目标替换内容N的过程,该题完成。

解答:

type Fill<
  T extends unknown[],
  N,
  Start extends number = 0,
  End extends number = T['length'],
  Count extends any[] = [],
  Flag extends boolean = Count['length'] extends Start ? true : false
> = Count['length'] extends End
  ? T
  : T extends [infer R, ...infer U]
    ? Flag extends false
      ? [R, ...Fill<U, N, Start, End, [...Count, 0]>]
      : [N, ...Fill<U, N, Start, End, [...Count, 0], Flag>]
    : T

type Demo = Fill<[], 0> // type Demo = []
type Demo2 = Fill<[], 0, 0, 3> // type Demo2 = []
type Demo3 = Fill<[1, 2, 3], 0, 0, 0> // type Demo3 = [1, 2, 3]
type Demo4 = Fill<[1, 2, 3], 0, 2, 2> // type Demo4 = [1, 2, 3]
type Demo5 = Fill<[1, 2, 3], 0> // type Demo5 = [0, 0, 0]
type Demo6 = Fill<[1, 2, 3], true> // type Demo6 = [true, true, true]
type Demo7 = Fill<[1, 2, 3], true, 0, 1> // type Demo7 = [true, 2, 3]
type Demo8 = Fill<[1, 2, 3], true, 1, 3> // type Demo8 = [1, true, true]
type Demo9 = Fill<[1, 2, 3], true, 10, 0> // type Demo9 = [1, 2, 3]
type Demo10 = Fill<[1, 2, 3], true, 0, 10> // type Demo10 = [true, true, true]

  1. Trim Right 实现 TrimRight ,它接收确定的字符串类型并返回一个新的字符串,其中新返回的字符串删除了原字符串结尾的空白字符串。
type Trimed = TrimLeft<'  Hello World  '> // 应推导出 '  Hello World'

思路:    首先定义出要承载过滤的字符类型的联合类型DeleteChar;接着使用模板字面量类型结构目标检测类型S,当检测到目标过滤字符串时,通过递归函数执行以外字符串,最后回抛对应数据。

解答:

type DeleteChar = ' ' | '\n' | '\t';
type TrimRight<S extends string> = S extends `${infer F}${DeleteChar}` ? TrimRight<F> : S;

  1. 去除数组指定元素: 实现一个像 Lodash.without 函数一样的泛型 Without<T, U>,它接收数组类型的 T 和数字或数组类型的 U 为参数,会返回一个去除 U 中元素的数组 T。
type Res = Without<[1, 2], 1>; // expected to be [2]
type Res1 = Without<[1, 2, 4, 1, 5], [1, 2]>; // expected to be [4, 5]
type Res2 = Without<[2, 3, 2, 3, 2, 3, 2, 3], [2, 3]>; // expected to be []

思路:    首先,定义一个_Exlcude方法,该方法作用于去除传入元祖T中存在P元素的值:通过infer关键字拆分元祖T属性内容,使用Equal 判断当前拆分判断元素是否为目标P元素,是的话过滤后组合递归函数接下去执行其他元素,反之,保留并且也接着递归执行其他元素; 然后,定义主体Without类型方法,该类型接受目标类型T和根据题意存在被过滤类型的元祖类型,先使用infer关键字结构过滤类型元祖,如果U属于元组类型,说明有多个值需要处理那么我们要使用_Exlcude<T, H> 先去除当前T中的第一个U中存在的需要过滤的类型H,在递归执行Without 执行U中下一个元素;如果U 不属于元组,只是单个字符串,说明只要过滤一个元素则直接执行 _Exlcude 操作即可;完成。 解答:

type _Exlcude<T extends any[], P> = T extends [infer H, ...infer R]
  ? Equal<H, P> extends true
    ? [..._Exlcude<R, P>]
    : [H, ..._Exlcude<R, P>]
  : [];

type Without<T extends any[], U> = U extends [infer H, ...infer R]
  ? Without<_Exlcude<T, H>, R>
  : _Exlcude<T, U>;