TypeScript体操,手动实现一些TS语法


theme: nico

TypeScript体操,手动实现一些TS语法

前言

TypeScript是JavaScript的超集,TypeScript 通过对原生 JavaScript 提供强类型加持,在很大程度上提升了代码质量。但我在刚开始使用TS的时候,真的太难受了。曾经的我爱上前端就是因为前端开发起来方便快捷,JS的类型可以随心所欲,HTML写完立马就能看到效果。而现在,来了TS,边写边学,边学边写,工作量几近与翻倍。

但是!同志,请相信我,认真对待写TS这件事,你将收获:

  1. 更好友好的开发体验,提示更友好
  2. TS声明即文档,减少了查文档的时间
  3. 复杂的大型项目,能够保证数据结构层级不错位
  4. 更好的抽象能力,部分枚举、模型和后端保持一致
  5. 规避低级错误,比如类型错误、函数传参错误、边界情况的判断

为什么要做TypeScript体操?

TS本身是一门语言,有自己的语法。从JS过来的前端工程师,往往都是瞄几眼TS官方文档就上手了,保证基本的日常开发没有问题就觉得掌握了TS这门语言的奥秘。但是在不经意间打开xx.d.ts的时候,尴尬的发现自己并看不懂别人写的TS声明文件,面试官让你手写一个TS的语法糖,你总是支支吾吾,好像会,又好像不太会。所以,希望通过这次的分享,希望可以达到以下几个目的:

  1. 从源码的角度理解每一个工具类型的实现机制
  2. 加深对TypeScript的理解
  3. 能够在平时的工作中举一反三,利用TypeScript为我们的代码保驾护航

关键字解释

typeof

在 TS 中用于类型表达时,typeof 可以用于从一个变量上获取它的类型。

const func = (a: number, b: number): number => {
  return a + b;
}
const obj = {
  name: 'zaoren',
  age: 11
}
​
type tupeFunc = typeof func; // type tupeFunc = (a: number, b: number) => number
type obj = typeof obj; // type obj = { name: string; age: number;}

keyof

keyof 的作用将一个 类型 映射为它 所有成员名称的联合类型

// 用 TS interface 描述对象
interface TsInterfaceObject {
  first: string;
  second: number;
}
​
// 用 TS type 描述对象
type TsTypeObject = {
  first: string;
  second: number;
};
​
// 用 Ts type 描述基本类型别名
type TsTypeAlias = string;
​
class JsClass {
  private priData: number;
  private priFunc() {}
​
  public pubData: number;
  public pubFunc1() {}
  public pubFunc2() {}
}
​
type NameUnionOfInterface = keyof TsInterfaceObject;
​
/**
 * 将所描述对象中的所有 key 组合成一个联合类型
 * type NameUnionOfType = "first" | "second"
 */
type NameUnionOfTypeObj = keyof TsTypeObject;
​
/**
 * 对一个非对象类型使用 keyof 后,会返回其 prototype 上所有 key 组合成的一个联合类型
 * type NameUnionOfTypeAlias = number | typeof Symbol.iterator | "toString" | "charAt" | ...
 */
type NameUnionOfTypeAlias = keyof TsTypeAlias;
​
/**
 * 其实也是返回 prototype 上所有 key(因为其实 private 私有部分实际是放在实例化对象中,而非原型)
 * type NameUnionOfClass = "pubData" | "pubFunc1" | "pubFunc2"
 */
type NameUnionOfClass = keyof JsClass;

in

in 可以按照js中的for..in遍历去理解,我们拿Pick这个语法糖来具体举例:[p in K]相当于就在遍历'name' | 'age'属性。

interface Person {
  name: string;
  age: number;
  location: string;
}
​
// 从 Person 中取出 'name' 属性
type pPersion = Pick<Person, 'name' | 'age'> // type pPersion = {  name: string; age: number }
​
/**
* Pick 的语法
*/
type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};

extends

extends关键字在TS编程中出现的频率挺高的,而且不同场景下代表的含义不一样

  • 类型继承,类型 A 去继承类型 B(interface 可用 extends 继承,type 不可以)

    interface IHuman {
      gender: "male" | "female";
      age: number;
    }
    ​
    interface IProgrammer {
      language: "java" | "php" | "javascript";
    }
    ​
    /**
     * interface 的继承
     * 可以同时继承多个 interface,它们之间用逗号分隔
     */
    interface IWebDeveloper extends IProgrammer, IHuman {
      skill: "vue" | "react";
    }
    ​
    const Me: IWebDeveloper = {
      skill: "react",
      language: "javascript",
      age: 18,
      gender: "male",
    };
    
  • 对类型进行条件约束

    在书写泛型的时候,我们往往需要对类型参数作一定的限制,比如希望传入的参数都有 name 属性的数组我们可以这么写:

    function getCnames<T extends { cname: string }>(entities: T[]):string[] {
     
      return entities.map(entity => entity.cname)
      
    }
    

这里extends对传入的参数作了一个限制,就是 entities 的每一项可以是一个对象,但是必须含有类型为stringcname属性。

  • 条件匹配

    // 判断范型 T 是否匹配于 number
    type OnlyWantNumber<T> = T extends number ? any : never;
    ​
    type Type1 = OnlyWantNumber<number>; // type Type1 = any
    ​
    type Type2 = OnlyWantNumber<string>; // type Type2 = never
    

    需要注意的是,如果 extends 左侧的类型为联合类型,会被拆解,就像数学中的分解因式子一样P<'x' | 'y'> => P<'x'> | P<'y'>

     type P<T> = T extends 'x' ? string : number;
    ​
     type A3 = P<'x' | 'y'>  // A3的类型是 string | number
    

infer

infer。它可以在 extends 关键字右侧的类型表达式中的任何位置使用。使用它可以为出现在该位置的任何类型命名

// 这里的 extends 在描述泛型
type Unpack<A> = A extends Array<infer E> ? E : A
​
type Test = Unpack<Apple[]> // => Apple
​
type Test = Unpack<Apple> // => Apple

Partial

使用Partial将T中所有的属性都变为非必须的

用法

type Foo = {
  a: string
  b: number
  c: boolean
}
​
const a: MyPartial<Foo> = {}

实现

  1. 首先使用 keyof T回 T 类型的所有键组成的一个联合类型 ,Foo类型则返回 string | number | boolean
  2. 使用in语法来遍历所有的key
  3. 将所有的属性改为非必须
type MyPartial<T> = {
  // 1. keyof T 会返回 T 类型的所有键组成的一个类型
  // 2. in 有点类似于 for...in 的语法。遍历所有的key
  [P in keyof T] ?: T[P]
}

Required

将类型T中的所有属性都变为必须的

用法

type Foo = {
  a?: string
  b?: number
  c?: boolean
}
const a: MyRequired<Foo> = {}
// Error
​
const e: MyRequired<Foo> = {
  a: 'BFE.dev',
  b: 123,
  c: true
}
// valid

实现

  1. 参考Partial,使用 keyofin语法来遍历所有的属性。
  2. 但需要注意的是! 这里不能直接使用 :来表示必须。因为使用[K in keyof T]语法,K 中的属性会保留它自身在T中的可选性。即之前如果是必填的,在新类型中还是必填的,如果是选填的还是选填的。有点类似一种“继承关系”。
  3. 所以,为了保证所有的属性都选填,可以使用-?语法,表示not非必填。
type MyRequired<T> = {
  // 1. 这里的 -?: 不能写成 :
  // 2. 因为使用[K in keyof T]语法,K 中的属性会保留它自身在T中的可选性。即之前如果是必填的,在新类型中还是必填的,如果是选填的同理。有点类似一种“继承关系”。
  [K in keyof T] -?: T[K];
};

ReadOnly

将类型T中的所有属性都变成仅读的

用法

type Foo = {
  a: string
}
​
const a:Foo = {
  a: 'BFE.dev',
}
a.a = 'bigfrontend.dev'
// OK
​
const b:MyReadonly<Foo> = {
  a: 'BFE.dev'
}
b.a = 'bigfrontend.dev'
// Error

实现

  1. 使用 [K in keyof T]语法,同时在属性前加上readonly关键字
// 将类型 T 中的所有属性都变为可选的
type MyReadonly<T> = {
  // readonly 是加载属性前面的
  readonly [K in keyof T]: T[K];
};

Record

构造一个 键类型属于K(某种类型或集合),值的类型为V的新类型

用法

type Key = 'a' | 'b' | 'c'
​
const a: Record<Key, string> = {
  a: 'BFE.dev',
  b: 'BFE.dev',
  c: 'BFE.dev'
}
a.a = 'bigfrontend.dev' // OK
a.b = 123 // Error
​
type Foo = MyRecord<{a: string}, string> // Error

实现

  1. 首先使用extends语法对K属性进行约束,因为做key只能是 string | number | symbol属性。
  2. 这里有一个取巧的办法,直接使用keyof any( keyof any 等价于 keyof string | number | symbol)
  3. 再使用 in语法遍历所有的key
// 1. 对K的类型定义使用的是 keyof any 等价于 string | number | symbol
// 2. 并且使用 extends 对K的类型进行约束。 type Foo = MyRecord<{a: string}, string>  就是无效的!!!
type MyRecord<K extends keyof any, V> = {
  // 3. 这里的 in 类似于 for...in 的语法。遍历所有的key
  [P in K]: V;
};

Pick

从类型 T 中取出一部分属性 K 组成一个新的类型返回

用法

type Foo = {
  a: string
  b: number
  c: boolean
}
​
type A = MyPick<Foo, 'a' | 'b'> // {a: string, b: number}
type B = MyPick<Foo, 'c'> // {c: boolean}
type C = MyPick<Foo, 'd'> // Error

实现

  1. 首先,使用extends对K的类型进行约束,必须是T的子集,用法中的第三个case可以看出,不是Foo的某个属性会报错
  2. 再用in关键字遍历K
  3. 使用类似于访问对象属性的写法,访问T中的某个属性的类型
// 这里的 K extends keyof T 必须取是 T 的子集
type MyPick<T, K extends keyof T> = {
  [P in K]: T[P];
};

Omit

从类型 T 中剔除一部分属性 K 组成一个新的类型返回

用法

type Foo = {
  a: string
  b: number
  c: boolean
}
​
type A = MyOmit<Foo, 'a' | 'b'> // {c: boolean}
type B = MyOmit<Foo, 'c'> // {a: string, b: number}
type C = MyOmit<Foo, 'c' | 'd'>  // {a: string, b: number}

实现

  1. Omit的实现需要借助 PickExclude
  2. 首先,先用 Keyof 拿到T中所有的属性
  3. 再从中剔除掉属性 K。那么剩下的就是需要的属性,Pick一下
  4. 这里要说明一下,为什么 K extends keyof any 要用 any。而不是像Pick一样用 K extends keyof T。因为 Pick的时候第二个参数必须是T中的某个属性,而Omit的时候不需要!!! 参考:用法中的第三个case
// 这里的实现需要借助 Exclude<K, U> 语法 和 Pick<T, K> 语法
// 1. 首先,先用 Keyof 拿到T中所有的属性
// 2. 再从中剔除掉属性 K。那么剩下的就是需要的属性,Pick一下
// 3. 这里要说明一下,为什么 K extends keyof any 要用 any。而不是像Pick一样用 K extends keyof T。因为 Pick的时候第二个参数必须是T中的某个属性,而Omit的时候不需要!!!
type MyOmit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

Exclude

将U联合类型的所有成员,从类型T中排除,可以理解为取差集

用法

// 排除一个具体的值
​
type T0 = Exclude<"a" | "b" | "c", "a">  // "b" | "c"
​
// 排除一种类型
​
type T1 = Exclude<string | number | (() => void), Function>

实现

  1. 使用extends关键字。之前在说 extends 关键字左侧的类型为联合类型,会被拆解,就像数学中的分解因式子一样。
  2. 使用never类型,never类型的意思是啥也不是。never会被认定为空的联合类型
type MyExclude<T, U> = T extends U ? never : T;

过程解读

type T0 = Exclude<"a" | "b" | "c", "a">  // "b" | "c"
​
// 根据 extends 分解因式的特性。上述语法等价于
type T0 = "a" extends "a" ? never : "a"   
  | "b" extends "a" ? never : "b"
  | "c" extends "a" ? never : "c"
​
// 等价于
type T0= never | "b" | "c" 
​
// 等价于
type T0 = "b" | "c"

Extract

选取 T 类型和 U 类型两者的公共部分并返回为一个新类型,可以理解为取交集。

用法

type T0 = Extract<"a" | "b" | "c", "a">  // "a"
​
type T1 = Extract<string | number | (() => void), Function> // () => void

实现

  1. 还是用 extends 字段,extends 左侧的类型为联合类型,会被因式分解。如果是 U 的
type MyExtract<T, U> = T extends U ? T : never

NonNullable

从类型T中剔除null | undefined 类型

用法

type Foo = 'a' | 'b' | null | undefined
​
type A = NonNullable<Foo> // 'a' | 'b'

实现

  1. 使用extends的因式分解属性,剔除null、undefined属性
type MyNonNullable<T> = T extends null | undefined ? never : T

Parameters

基于一个函数参数的类型构造一个元组类型(tuple type)。所以这个工具类型的作用就是获取函数参数的类型。

用法

type Foo = (a: string, b: number, c: boolean) => string
​
type A = Parameters<Foo> // [a:string, b: number, c:boolean]
type B = A[0] // string
type C = Parameters<{a: string}> // Error

实现

  1. 使用 extends 来约束 T的类型, T extends (...args: any) => any ,规定了 T 必须是一个函数( any 和 never除外),参数是 any 类型,所以参数可以是任意类型。
  2. 使用 infer关键字来获取入参的参数类型
  3. 返回的是一个元组类型
type MyParameters<T extends (...args: any) => void> = T extends (...args: infer P) => void ? P : never;

ConstructorParameters

把构造函数的参数类型作为一个元组类型返回。

用法

class Foo {
  constructor (a: string, b: number, c: boolean) {}
}
​
type C = ConstructorParameters<typeof Foo>  // [a: string, b: number, c: boolean]

实现

  1. 使用 extends 来约束T的类型必须是一个构造函数
  2. 使用 infer 关键字来获取入参的参数类型,并返回
type MyConstructorParameters<T extends new (...args: any) => any> =
T extends new (...args: infer P) => any ? P : never

ReturnType

获取函数的返回类型

用法

type Foo = () => {a: string}
​
type A = ReturnType<Foo> // {a: string}

实现

  1. extends关键字来约束T的类型必须是一个函数
  2. 使用infer关键字来获取返回类型,并返回
type MyReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer P ? P : never;

InstanceType

获取构造函数实例的返回类型。也就是对构造函数调用 new 操作符后的返回值类型。

用法

class Foo {}
type A = InstanceType<typeof Foo> // Foo
type B = InstanceType<() => string> // Error

实现

  1. 使用extends约束入参必须是一个构造函数类型
  2. 使用infer来获取返回值类型,并返回
type MyInstanceType<T extends new (...args: any) => any> = T extends new (
  ...args: any
) => infer T
  ? T
  : never;

ThisParameterType

提取一个函数类型显式定义的 this 参数,如果没有显式定义的 this 参数,则返回 unknown 。

用法

function Foo(this: {a: string}) {}
function Bar() {}
​
type A = ThisParameterType<typeof Foo> // {a: string}
type B = ThisParameterType<typeof Bar> // unknown

实现

  1. 由于this的参数名只能叫this,且参数名称必须在列表的第一个位置。所以,可以看出对于类型参数 T 是要严格匹配 (this: infer U, ...args: any[]) => any格式的。
  2. this 参数的类型定义一个类型参数 U ,在 extends 判断走 true 分支时返回 this 类型参数 U ,false 分支就返回 unknown。
// 1. 使用 A extends B 来限定入参的结构是 (this, ...args)这样的结构
// 2. 使用 infer U 来重命名 this的参数类型,并返回
type MyThisParameterType<T> = T extends (this: infer U, ...args: any) => any ? U : unknown;

OmitThisParameter

对于没有定义 this 参数类型的函数类型,直接返回这个函数类型,如果定义了 this 参数类型,就返回一个仅是去掉了 this 参数类型的新函数类型。

用法

function foo(this: {a: string}) {}
foo() // Error
​
const bar = foo.bind({a: 'BFE.dev'})
bar() // OK
​
​
type Foo = (this: {a: string}) => string
type Bar = OmitThisParameter<Foo> // () => string

实现

  1. 使用 A extends B 来限定入参的结构是 (this, …args)这样的结构
  2. 在返回的类型中,剔除掉 this。但是,这里要返回的是一个新函数,所以infer P、 infer U分别用来对入参类型和返回参数类型进行重命名。
type MyOmitThisParameter<T> = T extends (this: any, ...args: infer P) => infer U ? (...args: infer P) => infer U : unknown;

FirstChar

获取字符串第一个字符的类型

用法

type A = FirstChar<'BFE'> // 'B'
type B = FirstChar<'dev'> // 'd'
type C = FirstChar<''> // never

实现

  1. 在TS中,可以使用字符串模板的语法对字符串进行切割(又有点类似于正则表达式),${infer F}${any}能帮助我们拿到第一个字符串
// 1. 使用 infer 语法重命名 字符串的第一个字符和第二个字符
type FirstChar<T extends string> = T extends `${infer F}${any}` ? F : never;

LastChar

获取字符串的最后一个字符的类型

用法

type A = LastChar<'BFE'> // 'E'
type B = LastChar<'dev'> // 'v'
type C = LastChar<''> // never

实现

  1. infer F、infer R来切分字符串,infer F表示第一个字符串。
  2. R 是空字符串的时候,那么L就是最后一个字符串。
  3. 否则,递归此操作,直到R为空字符串为止,这个时候的 L就是我们要的结果
// 1. F 总是代表第一个字符串
// 2. R 是空字符串的时候,那么L就是最后一个字符串。否则 递归此操作,直到R为空字符串为止
type LastChar<T extends string> = T extends `${infer L}${infer R}`
  ? R extends ""
    ? L
    : LastChar<R>
  : never;

TupleToUnion

将元组类型转化成联合类型

用法

type Foo = [string, number, boolean]
​
type Bar = TupleToUnion<Foo> // string | number | boolean

实现

  1. 使用 extends类型约束,保证T是一个元组类型
  2. 使用TS中[infer L, ...infer Rest]的语法,将元组中的类型pop出来
  3. 将pop出来的类型使用|符号进行合并,生成联合类型。递归此过程
type TupleToUnion<T extends any[]> = T extends [infer L, ...infer Rest]
  ? L | TupleToUnion<Rest>
  : never;

FirstItem

获取元组类型中的一个

用法

type A = FirstItem<[string, number, boolean]> // string
type B = FirstItem<['B', 'F', 'E']> // 'B'

实现

  1. 使用TS中[infer L, ...infer Rest]的语法,获取元组中的第一个类型,返回
type FirstItem<T extends any[]> = T extends [infer L, ...infer Reset] ? L : never

LastItem

用法

type A = LastItem<[string, number, boolean]> // boolean
type B = LastItem<['B', 'F', 'E']> // 'E'
type C = LastItem<[]> // never

实现

  1. TS中的解构可以不用放在最后一个。所以可以使用[...infer Reset, infer L]
type LastItem<T extends any[]> = T extends [...infer L, infer R] ? R : never;

IsNever

判断是不是never类型

用法

type A = IsNever<never> // true
type B = IsNever<string> // false
type C = IsNever<undefined> // false

实现

  1. 在泛型中 never不能extends never(否则结果会直接变成never)。不过[never]可以extends [never]
type IsNever<T> = [T] extends [never] ? true : false;

StringToTuple

提取字符串类型的每个字符,生成一个元组类型

用法

type A = StringToTuple<'BFE.dev'> // ['B', 'F', 'E', '.', 'd', 'e','v']
type B = StringToTuple<''> // []

实现

  1. 取出字符串的第一个字符,放入元组

  1. 剩余的字符串递归此过程
type StringToTuple<T extends string> = T extends `${infer L}${infer R}`
  ? [L, ...StringToTuple<R>]
  : [];

LengthOfTuple

返回元组的长度

用法

type A = LengthOfTuple<['B', 'F', 'E']> // 3
type B = LengthOfTuple<[]> // 0

实现

可以直接调用泛型的.length属性

// 直接调用泛型的.length属性
type LengthOfTuple<T extends any[]> = T["length"]

LengthOfString

返回字符串的长度

用法

type A = LengthOfString<'BFE.dev'> // 7
type B = LengthOfString<''> // 0

实现

  1. 首先,字符串类型没有length属性。
  2. 但是可以把字符串转成元组类型,然后使用length属性
type LengthOfString<T extends string> = StringToTuple<T>["length"];

ReverseTuple

将元组类型Reverse一下

用法

type ReverseTuple<T extends any[]> = T extends [infer L, ...infer Reset]
  ? [...ReverseTuple<Reset>, L]
  : [];

实现

  1. 首先判断Tuple元组的结构类型是否能拆出 单个元组

  1. 将拆出来的元组放到元组最后面

  1. 递归此过程,直到元组的长度为0为止
type ReverseTuple<T extends any[]> = T extends [infer L, ...infer Reset]
  ? [...ReverseTuple<Reset>, L]
  : [];

Flat

将元组类型拍平

用法

type A = Flat<[1,2,3]> // [1,2,3]
type B = Flat<[1,[2,3], [4,[5,[6]]]]> // [1,2,3,4,5,6]
type C = Flat<[]> // []

实现

  1. 每次取出数组中的一项进行拍平,直到数组的长度为0为止
  2. 判断取出的这一项是否是数组,如果是则递归进行拍平,否则返回当前项
type Flat<T extends any[]> =
  // 1. 每次取出数组中的一项进行拍平,直到数组的长度为0为止
  T extends [infer F, ...infer R]
    ? // 2. 判断取出的这一项是否是数组,如果是则递归进行拍平,否则返回当前项
      [...(F extends any[] ? Flat<F> : [F]), ...Flat<R>]
    : [];

IsEmptyType

判断是否是空对象类型

用法

type A = IsEmptyType<string> // false
type B = IsEmptyType<{a: 3}> // false
type C = IsEmptyType<{}> // true
type D = IsEmptyType<any> // false
type E = IsEmptyType<object> // false
type F = IsEmptyType<Object> // false

实现

  1. 首先保证入参是一个对象,否则返回false
  2. 使用keyof T来获取所有的key,[keyof T] exnteds [never]来判断不存在key
type IsEmptyType<T> = T extends Record<string,string> ? [keyof T] extends [never] ? true: false: false

Shift

返回出去first item后的元组类型

用法

type A = Shift<[1,2,3]> // [2,3]
type B = Shift<[1]> // []
type C = Shift<[]> // []

实现

  1. 使用extends + infer语法
type Shift<T extends any[]> = T extends [infer P, ...infer Reset] ? Reset : []

IsAny

判断是否是any类型

用法

type A = IsAny<string> // false
type B = IsAny<any> // true
type C = IsAny<unknown> // false
type D = IsAny<never> // false

实现

  1. 首先要明确一个概念:any 和任何类型联合、交叉时候都等于 any 自身类型
  2. 那么可以巧妙的利用这个点, T & 1如果是any类型的话,那么T就是any类型,否则就不是。
  3. 这里要注意一下 T 为 unknown 类型的时候,
type IsAny<T> = 0 extends (T & 1) ? true : false;

Push

返回一个push后的新元组类型

用法

type A = Push<[1,2,3], 4> // [2,3]
type B = Push<[1], 2> // [1, 2]
type C = Push<[], string> // [string]

实现

  1. 直接往数组的最后加一个类型即可
type Push<T extends any[], I> = [...T, I]

RepeatString

类似于String.prototype.repeat(),返回T类型重复C次的结果

用法

type A = RepeatString<'a', 3> // 'aaa'
type B = RepeatString<'a', 0> // ''

实现

  1. 思路还是递归,用数组去保存组装好的数据
  2. 递归的出口:A['length'] extends C(3 = 3)
type RepeatString<
  T extends string,
  C extends number,
  A extends string[] = []
> = A["length"] extends C ? "" : `${T}${RepeatString<T, C, [T, ...A]>}`;

TupleToString

将元组类型转成字符串类型

用法

type A = TupleToString<['a']> // 'a'
type B = TupleToString<['B', 'F', 'E']> // 'BFE'
type C = TupleToString<[]> // ''

实现

  1. 取出一个往结果里面塞,剩下的递归
  2. 当取出来的为空字符串的时候递归结束
type TupleToString<T extends unknown[]> = T extends [infer L, ...infer R]
  ? L extends string
    ? `${L}${TupleToString<R>}`
    : never
  : "";

Repeat

返回一个重复的元组类型

用法

type A = Repeat<number, 3> // [number, number, number]
type B = Repeat<string, 2> // [string, string]
type C = Repeat<1, 1> // [1, 1]
type D = Repeat<0, 0> // []

实现

  1. 使用递归:递归的出口是 U["length"] extends C,当元组的长度达到C
  2. 递归的每次逻辑都往结果数组U中插入一个 T
type Repeat<T, C extends number, U extends T[] = []> = 
  U["length"] extends C ? U
  : Repeat<T, C, [...U, T]>

Filter

从元组类型T中过滤出能被分配给A类型的所有类型

用法

type A = Filter<[1,'BFE', 2, true, 'dev'], number> // [1, 2]
type B = Filter<[1,'BFE', 2, true, 'dev'], string> // ['BFE', 'dev']
type C = Filter<[1,'BFE', 2, any, 'dev'], string> // ['BFE', any, 'dev']

实现

  1. 递归:首先确定每层递归的入参是从元组中取出的类型、结果数组、当前元组
  2. 每层要做的事情:判断当前的元组是否是满足条件的,如果满足就往结果数组中push
  3. 递归结束的条件是:当前元组为空
/**
 * R:结果数组
*/
type Filter<T extends any[], A, R extends A[] = []> =
  // T 是空数组的时候,直接返回R
  T extends [infer F, ...infer O] 
    // 这里因为要兼容never类型,所以需要加上[], 满足条件则往结果数组 R中push取出的 F
    ? [F] extends [A]
      ? Filter<O, A, [...R, F]>
      : Filter<O, A, R>
    : R
© 版权声明
THE END
喜欢就支持一下吧
点赞6 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容