vlambda博客
学习文章列表

玩转Typescript类型,告别any

本文适合对TS使用不熟练,对TS有疑惑的小伙伴阅读~

欢迎关注前端早茶,与广东靓仔携手共同进阶~

一、前言

有不少小伙伴在日常开发项目中使用了Typescript,但是每每遇到一些类型声明的时候,就习惯性使用了any。
下面我们一起来看看TS的类型操作,以及它的原理~

二、正文

if 和 else

条件类型,条件类型冒号左边为  if  右边为  else
type A = 1
type B = 2
type Example = A extends B ? true : false // false

type Example = A extends B ? true : false 中的 true 和 false 即可以理解成它们分别为 if 分支和 else 分支中要写的代码

而 if 中的条件即为 A extends BA 是否可以分配给 B

要实现 else if 则需要多个这样的条件类型进行组合

模式匹配

type A = [123]
type ExampleA = A extends [infer First, ...infer Rest] ? First : never // 1
type B = "123"
type ExampleB = B extends `${infer FirstChar}${infer Rest}` ? FirstChar : never // '1'

模式匹配是我们要利用的最有用的 ts 特性之一,之后我们要实现的字符串的增删改查和元组的增删改查都要基于它

与或非

基于条件类型可以轻松实现与或非

// C 意为 Condition,条件
// common
// 与,即 C1,C2 同为真

type And<C1 extends boolean, C2 extends boolean> = C1 extends true
  ? C2 extends true
    ? true
    : false
  : false

// common
// 与,即 C1,C2 有一个为真

type Or<C1 extends boolean, C2 extends boolean> = C1 extends true
  ? true
  : C2 extends true
  ? true
  : false

// common
// 非,即反转 C 的真假状态

type Not<C extends boolean> = C extends true ? false : true

ts 目前不支持动态个数的泛型参数,因此如果有多个条件,我们需要定义多个不同的,比如

// common
// 有三个条件的情况

type And3<C1 extends boolean, C2 extends boolean, C3 extends boolean> = And<
  And<C1, C2>,
  C3
>

// common
// 有四个条件的情况

type And4<
  C1 extends boolean,
  C2 extends boolean,
  C3 extends boolean,
  C4 extends boolean
> = And<And3<C1, C2, C3>, C4>

现在,我们已经封装了若干个类型工具 And Or Not,要达成基于 ts 的类型系统实现加法器的目标

我们需要很多个这样的类型工具

为了方便管理,我们需要给它分模块,比如上面的与或非,我们划分在 common 里

我们还需要 function、array、number、object、string 这另外的五个,用于处理函数类型、元组类型、数字类型、对象类型、字符类型

判断相等

在 js 的运算操作符中,有 == 和 ===

在 ts 类型系统中,也可以实现类似的判断

// common
// 判断左侧类型是否可以分配给右侧类型

type CheckLeftIsExtendsRight<T extends any, R extends any> = T extends R
  ? true
  : false

// common
// 判断左侧类型是否和右侧类型一致

type IsEqual<A, B> = (<T>() => T extends A ? 1 : 2extends <
  T1
>() =>
 T1 extends B ? 1 : 2
  ? true
  : false

CheckLeftIsExtendsRight 即校验左侧类型是否可分配给右侧类型,和 == 不同的是,== 会进行类型转换后的比较,而条件类型 Left extends Right ? xxx : xxx 只会进行结构性兼容的检查

type Example1 = { a: 1; b: 2 } extends { a: 1 } ? true : false // true
type Example2 = 1 | 2 extends 1 ? true : false // true

虽然两个类型长的不一样,但是可以通过约束校验

toString

要实现 ts 的数学运算过于麻烦,因为数字是无法进行 infer 的,如何判断它(一个数字类型)为整型,浮点型?还是正数,或者负数?是不是仅仅有一个数字类型没有任何办法?

这都需要基于字符类型(或元组类型)的模式匹配

// string
// 将类型转为字符串有一定的限制,仅支持下面的类型

type CanStringified = string | number | bigint | boolean | null | undefined

// string
// 将支持的类型转化为字符串

type Stringify<T extends CanStringified> = `${T}`

效果

type Example1 = Stringify<0// "0"

type Example2 = Stringify<-1> // "-1"

type Example3 = Stringify<0.1// "0.1"

type Example4 = Stringify<"0.2"> // "0.2"

循环

在 js 中,我们可以通过 for、while、do...while 等循环进行可迭代对象的遍历,这些都离不开一个东西,那就是循环条件

比如在 for 循环中 for (初始化; 条件; 循环后逻辑) 我们一般用一个变量 i ,每一次循环后进行自增,循环条件一般是将 i 与另一个数比较大小

那么我们还需要实现一个数字类型大小比较,数字类型累加的工具类型

ts 中的循环可以通过递归来实现,ts 的类型系统中不存在类型赋值的概念

type Example = 1
Example = 2 // 没有这种写法

只有通过每次递归时,把当前泛型参数处理后,当做下一次递归的泛型参数,终止递归时,返回当前某一个泛型参数的类型

通过一个最简单的递归类型例子来看一下这个过程

type Example<
  C extends boolean = true,
  Tuple extends unknown[] = [1]
> = C extends true ? Example<false, [...Tuple, 1]> : Tuple

type Result = Example // [1, 1]

// Example 的两个泛型参数

如上示例,Result 得到的类型是 [1, 1]

第一次:C 是默认类型 true, 则会走到 Example<false, [...Tuple, 1]>,其中 [...Tuple, 1] 的结果为 [...[1], 1],即 [1, 1]

第二次:C 传入了 false,会走到 Tuple,Tuple 的值为上次传入的值 [1, 1],最后的返回类型为 [1, 1]

除了递归,还有两种方式可以循环,一种是分布式条件类型,还有一种是映射类型,但是它们都很难传递类型

// 分布式条件类型,当泛型参数 T 为联合类型时,条件类型即为分布式条件类型,会将 T 中的每一项分别分发给 extends 进行比对
type Example1<T> = T extends number ? T : never

type Result1 = Example1<"1" | "2" | 3 | 4// 3 | 4

// 映射类型,固定写法,in 操作符会分发 T 成为新对象类型的键
type Example2<T> = {
  [Key in T]: Key
}
type Result2 = Example2<"1" | "2" | 3 | 4// { 1: "1"; 2: "2"; 3: 3; 4: 4; }

基本的数学运算

判断正负

在部分场景下,我们可以兼容 number 类型的数字,也可以兼容 string 类型的数字,定义其为 NumberLike

type NumberLike = number | `${number}`
// 可分配给 NumberLike 的类型示例:number、`${number}`、1、-1、0.1、-0.1、"1"、"-1" 等

判断为 0

// N 意为 Number 数字
// number
// number类型是否为0,判断 N 是否可分配给 0 | "0"

type IsZero<N extends NumberLike> = common.CheckLeftIsExtendsRight<N, 0 | "0">

// number
// number类型是否大于0,泛型类型有限制 NumberLike,所以它一定是个数或者由数字构成的字符串,将其转为字符串后,判断最前面是不是 -,如果不是,就是大于零
type IsOverZero<N extends NumberLike> = IsZero<N> extends true
  ? false
  : common.CheckLeftIsExtendsRight<
      string.Stringify<N> extends `${"-"}${infer Rest}` ? Rest : never,
      never
    >

// number
// number类型是否小于0,对上面 IsOverZero 的结果取反
type IsLessZero<N extends NumberLike> = common.Not<IsOverZero<N>>

两数相加

在上面 循环 章节,我们讲到了,可以通过递归传递修改后的泛型参数,来创建复杂的工具类型

此场景下,我们可以生成动态的类型,最常见的有这几种,元组类型,模板字符串类型,联合类型

而元组类型的长度是可以访问的 如 [0, 1, 2]['length'] 结果为 3,且元组类型是可以拼接的,如 [...[0, 1, 2], ...[0]]['length'] 的长度为 4

那么我们可以动态生成两个指定长度的元组类型,然后拼接到一起,获取拼接后的元组长度,就可以得到正整数(和 0)的加法了

// array
// 构造长度一定(Length)的元组
type GetTuple<Length extends number = 0> = GetTupleHelper<Length>

type GetTupleHelper<
  Length extends number = 0,
  R extends unknown[] = []
> = R["length"extends Length ? R : GetTupleHelper<Length, [...R, unknown]>
type IntAddSingleHepler<N1 extends number, N2 extends number> = [
  ...array.GetTuple<N1>,
  ...array.GetTuple<N2>
]["length"]

// number
// 正整数(和0)加法,T1,T2最大999
type IntAddSingle<N1 extends number, N2 extends number> = IntAddSingleHepler<
  N1,
  N2
extends number
  ? IntAddSingleHepler<N1, N2>
  : number

比较大小

如果想要实现元组类型的排序,那就必须要能够比较数字大小

如何实现数字类型大小的比较呢?

还是得基于元组

基于两个数 N1、 N2,创建不同的元组 T1T2,依次减少两个元组的长度(删除第一位或最后一位),当有一个元组长度为 0 时,就是这个元组对应的数字类型,比另一个数字类型小(或相等,所以也要先判断是否不相等才进行比较)

基于模式匹配,匹配出最后一项,和剩余项,并返回剩余项

类型系统中没有改变原类型的概念,因此元组类型的增删改查都应该直接返回修改后的类型,而不是修改后的变化值

如在 js 中,[1, 2, 3].shift() 会返回 1[1, 2, 3].pop() 会返回 3,但是 ts 类型系统中,这样返回是没有意义的,Pop<[1, 2, 3]> 应该得到类型 [1, 2]

// array
// 去掉数组的最后一位
type Pop<T extends unknown[]> = T extends [...infer LeftRest, infer Last]
  ? LeftRest
  : never
// T 意为 Tuple 元组
type CompareHelper<
  N1 extends number,
  N2 extends number,
  T1 extends unknown[] = array.GetTuple<N1>,
  T2 extends unknown[] = array.GetTuple<N2>
> = IsNotEqual<N1, N2, trueextends true
  ? common.Or<IsZero<T1["length"]>, IsZero<T2["length"]>> extends true
    ? IsZero<T1["length"]> extends true
      ? false
      : true
    : CompareHelper<array.Pop<T1>["length"], array.Pop<T2>["length"]>
  : false

// number
// 比较两个数字类型大小
type Compare<N1 extends number, N2 extends number> = CompareHelper<N1, N2>

两数相减

两个数字类型相减的逻辑与两个数字类型比较大小的逻辑类似,但是返回类型时,会返回剩余长度多的元组的长度

这个实现受到元组类型长度的限制,只能得到正数(或 0),即结果的绝对值

且用在其他工具类型中时,会出现 类型实例化过深,并可能无限(Type instantiation is excessively deep and possibly infinite) 的报错

即:目前有 50 个嵌套实例的限制,可以通过批处理规避限制 

// 批处理示例
type GetLetters<Text> =
  Text extends `${infer C0}${infer C1}${infer C2}${infer C3}${infer C4}${infer C5}${infer C6}${infer C7}${infer C8}${infer C9}${infer Rest}`
    ? C0 | C1 | C2 | C3 | C4 | C5 | C6 | C7 | C8 | C9 | GetLetters<Rest>
    : Text extends `${infer C}${infer Rest}`
    ? C | GetLetters<Rest>
    : never

减法实现

type IntMinusSingleAbsHelper<
  N1 extends number,
  N2 extends number,
  T1 extends unknown[] = array.GetTuple<N1>,
  T2 extends unknown[] = array.GetTuple<N2>
> = IsNotEqual<N1, N2, trueextends true
  ? common.Or<IsZero<T1["length"]>, IsZero<T2["length"]>> extends true
    ? IsZero<T1["length"]> extends true
      ? T2["length"]
      : T1["length"]
    : IntMinusSingleAbsHelper<array.Pop<T1>["length"], array.Pop<T2>["length"]>
  : 0

// number
// 两个数字类型相减,得到绝对值
type IntMinusSingleAbs<
  N1 extends number,
  N2 extends number
> = IntMinusSingleAbsHelper<N1, N2>

虽然有嵌套深度的限制,写好的减法不能用,但是加法是很好用的,有加法我们一样可以写出很多逻辑

三、参考 js 封装工具类型

CharAt - 获取字符串在索引位 I 下的 字符

/**
 * 获取字符串在索引位 I 下的 字符
 * @example
 * type Result = CharAt<"123", 1> // "2"
 */

type CharAt<S extends string, I extends number> = Split<S>[I]

原理:元组类型可以进行索引访问,可以将字符串类型按照 '' 分割成元组类型,然后通过 索引访问,得到索引位 I 处的字符

Concat - 拼接两个字符串

/**
 * 拼接两个字符串
 * @example
 * type Result = Concat<"123", "456"> // "123456"
 */

type Concat<S1 extends string, S2 extends string> = `${S1}${S2}`
原理:TS 模板字符串类型用法

Includes - 判断字符串是否包含子串

/**
 * 判断字符串是否包含子串
 * @example
 * type Result = Includes<"123", "12"> // true
 */

type Includes<
  S1 extends string,
  S2 extends string
> = S1 extends `${infer Left}${S2}${infer Right}` ? true : false
原理:模式匹配可判断字符串类型中是否含有子串

IndexOf - 从左往右查找子串的位置

type IndexOfHelper<
  S1 extends string,
  S2 extends string,
  Len1 extends number = GetStringLength<S1>,
  Len2 extends number = GetStringLength<S2>
> = common.Or<
  number.Compare<Len1, Len2>,
  number.IsEqual<Len1, Len2>
extends true
  ? S1 extends `${infer Left}${S2}${infer Right}`
    ? GetStringLength<Left>
    : -1
  : -1

/**
 * 从左往右查找子串的位置
 * @example
 * type Result = IndexOf<"123", "23"> // 1
 */

type IndexOf<S1 extends string, S2 extends string> = IndexOfHelper<S1, S2>

原理:匹配出 ${infer Left}${S2}${infer Right} 中 Left,求其长度,则索引位即为 Left 的长度,如果匹配不到,返回 -1

可以先比较父串和子串的长度,如果子串比父串还长,那就不需要匹配了,直接返回 -1

LastIndexOf - 从右往左查找子串的位置

type LastIndexOfHelper<
  S1 extends string,
  S2 extends string,
  Index extends number = -1 /** 当前从左往右匹配最大的值,匹配不到以后,上一次匹配的索引就是从右往左第一个的索引 */,
  AddOffset extends number = 0 /** 每次从左往右匹配并替换成空串后,下次循序需要累加的值 */
> = S1 extends `${infer Left}${S2}${infer Right}`
  ? LastIndexOfHelper<
      Replace<S1, S2, "">,
      S2,
      number.IntAddSingle<GetStringLength<Left>, AddOffset>,
      number.IntAddSingle<AddOffset, GetStringLength<S2>>
    >
  : Index

/**
 * 从右往左查找子串的位置
 * @example
 * type Result = LastIndexOf<"23123", "23"> // 3
 */

type LastIndexOf<S1 extends string, S2 extends string> = LastIndexOfHelper<
  S1,
  S2
>

原理:模板字符串类型的模式匹配是从左往右的,而 LastIndexOf 是从右往左的,所以在匹配时,仍然基于从左往右匹配,但是每次匹配后,替换掉匹配过的子串为空串

然后把删掉的部分的长度累计起来,结果就是模拟从右往左匹配到的索引值

Replace - 在字符串中查找并替换一处子串

/**
 * 在字符串中查找并替换一处子串
 * @example
 * type Result = Replace<"23123", "23", "xx"> // "xx123"
 */

type Replace<
  S extends string,
  MatchStr extends string,
  ReplaceStr extends string
> = S extends `${infer Left}${MatchStr}${infer Right}`
  ? `${Left}${ReplaceStr}${Right}`
  : S
原理:基于模板字符串的模式匹配,匹配到了就用  ReplaceStr  换掉  MatchStr

ReplaceAll - 在字符串中查找并替换所有子串

/**
 * 在字符串中查找并替换所有子串
 * @example
 * type Result = Replace<"23123", "23", "xx"> // "xx1xx"
 */

type ReplaceAll<
  S extends string,
  MatchStr extends string,
  ReplaceStr extends string
> = Includes<S, MatchStr> extends true
  ? ReplaceAll<Replace<S, MatchStr, ReplaceStr>, MatchStr, ReplaceStr>
  : S
原理:基于  Replace ,递归进行替换,替换掉所有  MatchStr ,终止条件是  S  是否包含  MatchStr

Trim - 去掉字符串的空格

/**
 * 去掉字符串类型两侧的空格
 * @example
 * type Result = PadStart<'   0123   '> // '0123'
 */

type Trim<S extends string> = TrimLeft<TrimRight<S>>
原理:先用  TrimRight  去掉右侧的空格,再把前者结果交给  TrimLeft  去掉左侧的空格

ToUpperCase - 字符串转大写

/**
 * 字符串转大写
 * @example
 * type Result = ToUpperCase<'abc'> // 'ABC'
 */

type ToUpperCase<S extends string> = Uppercase<S>
原理:TS 内置

ToLowerCase - 字符串转小写

/**
 * 字符串转小写
 * @example
 * type Result = ToUpperCase<'ABC'> // 'abc'
 */

type ToLowerCase<S extends string> = Lowercase<S>
原理:TS 内置

SubString - 截取 start(包括)到 end(不包括)之间的字符串

type SubStringHelper<
  S extends string,
  Start extends number,
  End extends number,
  Offset extends number = 0,
  Cache extends string[] = []
> = number.IsEqual<Offset, End> extends true
  ? array.Join<Cache, "">
  : SubStringHelper<
      S,
      Start,
      End,
      number.IntAddSingle<Offset, 1>,
      common.And3<
        common.Or<number.Compare<Offset, Start>, number.IsEqual<Offset, Start>>,
        common.Or<number.Compare<End, Offset>, number.IsEqual<Offset, End>>,
        CharAt<S, Offset> extends string ? true : false
      > extends true
        ? array.Push<Cache, CharAt<S, Offset>>
        : Cache
    >
/**
 * 截取start(包括)到end(不包括)之间的字符串
 * @example
 * type Result = SubString<'123', 0, 1> // '1'
 */

type SubString<
  S extends string,
  Start extends number,
  End extends number
> = SubStringHelper<S, Start, End>
原理:遍历字符串类型的每一个字符,如果当前索引大于等于  Start ,并且小于等于  End ,就把当前字符 push 到元组中,最后用  array.Join ,将元组转为字符串类型

SubStr - 在字符串中抽取从开始下标到结束下标的字符

/**
 * 在字符串中抽取从 开始 下标开始的指定数目的字符
 * @example
 * type Result = SubStr<'123', 1, 2> // '23'
 */

type SubStr<
  S extends string,
  Start extends number,
  Len extends number
> = SubStringHelper<S, Start, number.IntAddSingle<Start, Len>>
原理: SubString  需要起始和结束,有  Start  和  Len  就可以先算出  End ,就可以使用  SubString  了

Pop - 去除元组类型的最后一位

/**
 * 去掉元组的最后一位
 * @see https://juejin.cn/post/7045536402112512007#heading-2
 * @example
 * type Result = Pop<[1, 2, 3]> // [1, 2]
 */

type Pop<T extends unknown[]> = T extends [...infer LeftRest, infer Last]
  ? LeftRest
  : never
原理:基于元组的模式匹配,提取最后一项,返回剩余项

Shift - 去除元组类型的第一位

/**
 * 去掉数组的第一位
 * @example
 * type Result = Shift<[1, 2, 3]> // [2, 3]
 */

type Shift<T extends unknown[]> = T extends [infer First, ...infer RightRest]
  ? RightRest
  : never

原理:与 Pop 同理

UnShift - 在元组前面插入一位

/**
 * 在元组前面插入一位
 * @example
 * type Result = UnShift<[1, 2, 3], 0> // [0, 1, 2, 3]
 */

type UnShift<T extends unknown[], Item> = [Item, ...T]
原理: []  中直接写类型可以构建新元组类型,其中写  ...Tuple ,与 js 中的扩展运算符效果一致

Push - 在元组后面插入一位

/**
 * 在元组最后插入一位
 * @example
 * type Result = Push<[1, 2, 3], 4> // [1, 2, 3, 4]
 */

type Push<T extends unknown[], Item> = [...T, Item]
原理:同  UnShift

Concat - 合并两个元组类型

/**
 * 合并两个元组类型
 * @example
 * type Result = Concat<[1, 2, 3], [4]> // [1, 2, 3, 4]
 */

type Concat<T extends unknown[], R extends unknown[]> = [...T, ...R]
原理:见  UnShift

Join - 将元组类型拼接成字符串类型

/**
 * 将元组类型拼接成字符串类型
 * @example
 * type Result = Join<[1, 2, 3]> // "1,2,3"
 */

type Join<
  T extends string.CanStringified[],
  SplitStr extends string.CanStringified = ""
> = T["length"extends 0
  ? ""
  : T extends [infer Left, ...infer RightRest]
  ? Left extends string.CanStringified
    ? RightRest extends string.CanStringified[]
      ? `${Left}${T["length"extends 1 ? "" : SplitStr}${Join<
          RightRest,
          SplitStr
        >}
`

      : never
    : never
  : never

原理:每次递归时提取元组第一个类型,然后将此类型放到模板字符串类型的第一个位置 ${第一个位置}${第二个位置}${第三个位置}

第二个位置即转为字符串用来分隔的子串,如果元组的长度为 0,则为空串

第三个位置则是剩下部分的逻辑,即重复最开始的逻辑

Every - 校验元组中每个类型是否都符合条件

type EveryHelper<
  T extends unknown[],
  Check,
  Offset extends number = 0,
  CacheBool extends boolean = true
> = T["length"extends Offset
  ? CacheBool
  : EveryHelper<
      T,
      Check,
      number.IntAddSingle<Offset, 1>,
      common.And<common.CheckLeftIsExtendsRight<T[Offset], Check>, CacheBool>
    >

/**
 * 校验元组中每个类型是否都符合条件
 * @example
 * type Result = Every<[1, 2, 3], number> // true
 */

type Every<T extends unknown[], Check> = T["length"extends 0
  ? false
  : EveryHelper<T, Check>
原理:初始类型  CacheBool  为 true,依次将元组中每个类型与初始类型进行   操作,如果元组长度为 0,则返回  false

Some - 校验元组中是否有类型符合条件

type SomeHelper<
  T extends unknown[],
  Check,
  Offset extends number = 0,
  CacheBool extends boolean = false
> = T["length"extends Offset
  ? CacheBool
  : SomeHelper<
      T,
      Check,
      number.IntAddSingle<Offset, 1>,
      common.Or<common.CheckLeftIsExtendsRight<T[Offset], Check>, CacheBool>
    >

/**
 * 校验元组中是否有类型符合条件
 * @example
 * type Result = Every<['1', '2', 3], number> // true
 */

type Some<T extends unknown[], Check> = SomeHelper<T, Check>
原理:初始类型  CacheBool  为 false,依次将元组中每个类型与初始类型进行   操作,如果元组长度为 0,则返回  false

Fill - 以指定类型填充元组类型

type FillHelper<
  T extends unknown[],
  F,
  Offset extends number = 0
> = T["length"extends 0
  ? F[]
  : Offset extends T["length"]
  ? common.IsEqual<T, F[]> extends true /** any[] -> T[] */
    ? T
    : F[]
  : FillHelper<array.Push<array.Shift<T>, F>, F, number.IntAddSingle<Offset, 1>>


/**
 * 以指定类型填充元组类型
 * @example
 * type Result = Fill<['1', '2', 3, any], 1> // [1, 1, 1, 1]
 */

type Fill<T extends unknown[], F = undefined> = FillHelper<T, F>

原理:如果原元组长度为 0,则直接返回由新类型构成的元组 F[]

如果是数组类型如:any[]never[]number[],也应该直接替换成 T[]

否则,每次在原元组中删除第一个,然后在最前面添加一个新类型,直到循环条件与  T  的长度一致时,终止递归

Filter - 过滤出元组类型中符合条件的类型

type FilterHelper<
  T extends unknown[],
  C,
  Strict extends boolean,
  Offset extends number = 0,
  Cache extends unknown[] = []
> = Offset extends T["length"]
  ? Cache
  : FilterHelper<
      T,
      C,
      Strict,
      number.IntAddSingle<Offset, 1>,
      common.And<Strict, common.IsEqual<T[Offset], C>> extends true
        ? array.Push<Cache, T[Offset]>
        : common.And<
            common.Not<Strict>,
            common.CheckLeftIsExtendsRight<T[Offset], C>
          > extends true
        ? array.Push<Cache, T[Offset]>
        : Cache
    >

/**
 * 过滤出元组类型中符合条件的类型
 * @example
 * type Result = Filter<['1', '2', 3, any, 1], 1, true> // [1]
 */

type Filter<
  T extends unknown[],
  C,
  Strict extends boolean = false
> = FilterHelper<T, C, Strict>

原理:严格模式,即 any 只能为 any,而不能为 1unknown 这样的其他类型

如果是严格模式就用 common.IsEqual 进行约束校验,否则用 common.CheckLeftIsExtendsRight 进行约束校验

每次递归时,如果满足上述条件,则放入新的元组类型中

如果循环条件 Offset 等于 T 的长度是,终止循环,返回新的元组类型 Cache

Find - 找到元组类型中第一个符合条件的类型

type FindHelper<
  T extends unknown[],
  C,
  Offset extends number = 0
> = Offset extends number.IntAddSingle<T["length"], 1>
  ? null
  : common.CheckLeftIsExtendsRight<T[Offset], C> extends true
  ? T[Offset]
  : FindHelper<T, C, number.IntAddSingle<Offset, 1>>
/** */
type Find<T extends unknown[], C> = FindHelper<T, C>
原理:遍历在元组中找,如果找到了匹配的,则返回该类型,否则返回  null  类型

Reverse - 反转元组

type ReverseHelper<
  T extends unknown[],
  Offset extends number = 0,
  Cache extends unknown[] = []
> = Cache["length"extends T["length"]
  ? Cache
  : ReverseHelper<T, number.IntAddSingle<Offset, 1>, UnShift<Cache, T[Offset]>>
/** */
type Reverse<T extends unknown[]> = ReverseHelper<T>
原理:遍历老元组类型,每次在新元组类型  Cache  的前面插入当前类型

FindIndex - 找到元组类型中第一个符合条件的类型的索引

type FindIndexHelper<
  T extends unknown[],
  C,
  Strict extends boolean = false,
  Offset extends number = 0
> = Offset extends number.IntAddSingle<T["length"], 1>
  ? -1
  : common.And<common.IsEqual<T[Offset], C>, Strict> extends true
  ? Offset
  : common.And<
      common.CheckLeftIsExtendsRight<T[Offset], C>,
      common.Not<Strict>
    > extends true
  ? Offset
  : FindIndexHelper<T, C, Strict, number.IntAddSingle<Offset, 1>>

/** */
type FindIndex<
  T extends unknown[],
  C,
  Strict extends boolean = false
> = FindIndexHelper<T, C, Strict>
原理:严格模式,参考上文  Filter ,遍历元组,符合约束校验时,返回当前  Offset ,否则结束后返回 -1

Flat - 扁平化元组

type FlatHelper<
  T extends unknown[],
  Offset extends number = 0,
  Cache extends unknown[] = []
> = Offset extends T["length"]
  ? Cache
  : FlatHelper<
      T,
      number.IntAddSingle<Offset, 1>,
      T[Offset] extends unknown[]
        ? Concat<Cache, T[Offset]>
        : Push<Cache, T[Offset]>
    >

type Flat<T extends unknown[]> = FlatHelper<T>
原理:遍历元组类型,如果当前类型不满足  unknown[]   的约束,则将它  Push  进新元组中,否则将它  Concat  进去

Includes - 元组类型中是否存在一个符合条件的类型

type Includes<T extends unknown[], C> = common.CheckLeftIsExtendsRight<
  C,
  TupleToUnion<T>
>

原理:将元组转为联合类型,如果约束条件 C 可以分配给该联合类型,那么就是 true

Slice - 提取元组类型中指定起始位置到指定结束位置的类型构造新元组类型

type SliceHelper<
  T extends unknown[],
  Start extends number,
  End extends number,
  Offset extends number = 0,
  Cache extends unknown[] = []
> = number.IsEqual<Offset, End> extends true
  ? Cache
  : SliceHelper<
      T,
      Start,
      End,
      number.IntAddSingle<Offset, 1>,
      common.And3<
        common.Or<number.Compare<Offset, Start>, number.IsEqual<Offset, Start>>,
        common.Or<number.Compare<End, Offset>, number.IsEqual<Offset, End>>,
        common.Or<
          number.Compare<T["length"], Offset>,
          number.IsEqual<T["length"], End>
        >
      > extends true
        ? array.Push<Cache, T[Offset]>
        : Cache
    >

type Slice<
  T extends unknown[],
  Start extends number,
  End extends number
> = SliceHelper<T, Start, End>

原理:和字符串裁剪的类似,遍历老元组,当循环条件 Offset 大于等于 Start 或 小于等于 End 时,将这些类型 Push 到新元组中

Sort - 排序

type SortHepler2<
  T extends number[],
  Offset extends number = 0,
  Offset1 extends number = 0,
  Offset1Added extends number = number.IntAddSingle<Offset1, 1>,
  Seted1 extends unknown[] = ArraySet<T, Offset1Added, T[Offset1]>,
  Seted2 extends unknown[] = ArraySet<Seted1, Offset1, T[Offset1Added]>
> = number.IntAddSingle<
  number.IntAddSingle<Offset, Offset1>,
  1
extends T["length"]
  ? SortHepler1<T, number.IntAddSingle<Offset, 1>>
  : SortHepler2<
      number.Compare<T[Offset1], T[Offset1Added]> extends true
        ? Seted2 extends number[]
          ? Seted2
          : never
        : T,
      number.IntAddSingle<Offset1, 1>
    >

type SortHepler1<
  T extends number[],
  Offset extends number = 0
> = Offset extends T["length"] ? T : SortHepler2<T, Offset>

type Sort<T extends number[]> = SortHepler1<T>

原理:最简单的冒泡排序,每次排序时,将大的与小的置换


文章转载于:https://juejin.cn/post/7061556434692997156

玩转Typescript类型,告别any

面试题库推荐


  
玩转Typescript类型,告别any
  
玩转Typescript类型,告别any
  
玩转Typescript类型,告别any

四、最后

这是类型的具体操作,弄清楚它的原理对ts的某一方面有更高的理解,可以了解一下,之后在日常开发TS的时候会更佳得心应手~

关注我,一起携手进阶

欢迎关注前端早茶,与广东靓仔携手共同进阶