ts难点记录-工具泛型使用及其实现

152 阅读6分钟

关键字

extends

  • 可以用来继承一个class,interface,还可以用来判断有条件类型(很多时候在ts看到extends,并不是继承的意识),
  • T extends U ? X : Y,意思是:若 T 能够赋值给 U,那么类型是 X,否则为 Y,原理是令 T' 和 U' 分别为 T 和 U 的实例,并将所有类型参数替换为 any,如果 T' 能赋值给 U',则将有条件的类型解析成 X,否则为Y
type Words = 'a'|'b'|"c";

type W<T> = T extends Words ? true : false;

type WA = W<'a'>; // -> true

type WD = W<'d'>; // -> false

// a 可以赋值给 Words 类型,所以 WA 为 true,
// 而 d 不能赋值给 Words 类型,所以 WD 为 false。

keyof

  • keyof 可以用来取得一个对象接口的所有 key 值:
interface Person {
  name: string,
  age: number,
  sex: string,
}

type K1 = keyof Person // 对象Person所以的key: 'name', 'age', 'sex'
type K2 = keyof Person[] // 'length', 'push','pop'...
type K3 = keyof {[x:string]: Person ]} // string | number

typeof

  • 在 JS 中 typeof 可以判断数据类型,在 TS 中,它还有一个作用,就是获取一个变量的声明类型,如果不存在,则获取该类型的推论类型
interface IDog {
  name: string;
  age: number;
  sex?: string;
}
const jack: IDog= {
  name: 'jack',
  age: 100,
}

function foo(x:number):Array<number>{
    return [x]
}

type F = typeof foo; // (x: number) => number[]

内置类型

Partial

在某些情况下,我们希望类型中的所有属性都不是必需的,只有在某些条件下才存在,我们就可以使用Partial来将已声明的类型中的所有属性标识为可选的

type Partical<T> = { //T中的所有属性都是可选的
  [P in keyof T]: T[p]
 }
interface Person {
   name: string,
   weight: string,
   age: number,
   job: string,
}

type ParticalPerson = Partical<Person>

  // 等价于
  type ParticalPerson  = {
    name?: string,
     weight?: string,
     age?: number,
     job?: string,
  }


let someone: ParticalPerson = {
  age: 12,
  name:'lili'
}
  • 在上述示例中, 我们使用Partial将所有属性标识为可选的,因此最终someone对象中虽然只包含age和name属性,但是编译器依旧没有报错,所以当我们不能明确地确定对象中包含哪些属性时,我们就可以通过Partial来声明

Required

Required 作用刚好跟 Partial 相反,Partial 是将所有属性改成可选项,Required 则是将所有类型改成必选项

  • 其中 -? 是代表移除 ? 这个 modifier 的标识。
  • 与之对应的还有个 +? , 这个含义自然与 -? 之前相反, 它是用来把属性变成可选项的,+ 可省略,见 Partial
interface Person {
 age: number;
 name: string;
 sex: string;
}
 
type RequiredPerson = Required<Dog>;
// 等价于
type RequiredPerson = {
 age: number;
 name: string;
 sex: string;
}
 
let hly: RequiredPerson = {
 age: 12,
 name: 'hly',
 sex: 'f'
};

Readonly

给子属性添加 readonly 的标识,设置为只读,

type  Readonly = {
  readonly: [P in keyof T]: T[p] // 将所有属性设置为只读
}
// 如果将上面的 readonly 改成 -readonly, 就是移除子属性的 readonly 标识
interface IDog {
  name: string, 
  age: number
}

type TDog = Readonly<iDog>
  class MiniDogForTest {
    run(){
      let dog:IDog = {
        name: 'mini',
        age: 1,
      }
      dog.name  = 'kuku'
       let dog2: TDog = {
            name: 'wiwi',
            age: 1
        };
      dog2.age = 2 // 报错,不能赋值
    }
  }

Pick

type Pick<T, K extends keyof T> = { // 从T中,选择一组键在并集K中的属性
  [P in K]: T[P] //  K 必须是 T 的 key,用 in 进行遍历, 将值赋给 P, 最后 T[P] 取得相应属性的值
}
  • 示例:
interface IPerson {
 age: number;
 name: string;
 sex: string;
  weight: number,
  height: number
}


type PickP = Pick<IPerson, 'age' | 'sex' | 'name'>
  // 等价于
type PickP = {
 name: string;
 age: number;
 sex: string;
};

let lili: PickP = {
 name: 'yiyi',
 age: 4,
 height: 100
};
  • 在上述示例中,由于我们只关心IPerson对象中的name,age和sex是否存在,因此我们就可以使用Pick从IPerson 接口中拣选出我们关心的属性而忽略其他属性的编译检查

Record

可以根据 K 中的所有可能值来设置 key,以及 value 的类型

type Record<K extends keyof any, T> = { // 构造一个具有一组属性K(类型T)的类型
  [P in K]: T
}



let person = Record<string, string | number | undefined>; // -> string | number | undefined

该类型可以将 K 中所有的属性的值转化为 T 类型,并将返回的新类型返回给person,K可以是联合类型、对象、枚举等等

  • 示例:
type personGroup = 'mini1' || 'mini2'
interface IPerson {
  name: string,
  age: number
}

type IPers = Record<personGroup, IPerson>
  const personInfo:IPers = {
    mini1:{
        name:'wangcai',
        age:2
    },
    mini2:{
        name:'xiaobai',
        age:3
    },
}

Exclude

从T中排除那些可分配给U的类型: type Exclude<T,U> = T extends U ? never : T

Exclude用于排除掉我们不需要关心的属性

interface IPerson {
 name: string;
 age: number;
 height: number;
 weight: number;
 sex: string;
}
 
type keys = keyof IPerson; // -> "name" | "age" | "height" | "weight" | "sex"
 
type ExcludePerson = Exclude<keys, "name" | "age">;
// 等价于
type ExcludePerson = "height" | "weight" | "sex";

Omit

构造一个除类型K之外的T属性的类型: type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>,即Exclude来排除掉其他不需要的属性

  • 示例:
interface IPerson {
name: string;
age: number;
height: number;
weight: number;
sex: string;
}

// 表示忽略掉User接口中的name和age属性
type OmitPerson = Omit<IPerson, "name" | "age">;
// 等价于
type OmitPerson = {
  height: number;
  weight: number;
  sex: string;
};

let dog: OmitPerson = {
height: 1,
weight: 'wangcai',
sex: 'boy'
};

在上述示例中,需要忽略掉IPerson接口中的name和age属性,则只需要将接口名和属性传入Omit即可,对于其他类型也是如此,大大提高了类型的可扩展能力,方便复用

Extrac

从T中提取可分配给U的类型 type Extrac<T, U> = T extends U ? T : never=> 从 T 中提取出 U

即: 如果 T 能赋值给 U 类型的话,那么就会返回 T 类型,否则返回 never,最终结果是将 T 和 U 中共有的属性提取出来

type test = Extract<'a' | 'b' | 'c' | 'd', 'a' | 'c' | 'f'|'g'>;  // -> 'a' | 'c'

ReturnType

获取函数类型的返回类型

type ReturnType<T extends (...args: any) => any> =  T extends (...args: any) => infer R ? R : any
  • 获取类型的返回类型
  • 其实这里的infer R就是一个变量来承载函数签名的返回值类型,即用它取到函数返回值的类型方便之后使用
  • 实际使用的话,可以通过ReturnType拿到函数的返回类型
function foo(x:number): Array<number>{
  return [x]
}
type fn = ReturnType<typeof foo> // number

NonNullable

从T中排除null和undefined:

type NoNullable<T> = T extends null | undefined ? never : T

这个类型可以用来过滤类型中的 null 及 undefined 类型。

  • 示例:
type test = string | number | null
type test1 = NoNullable<test> // string | number 

Parameters

在元组中获取构造函数类型的参数

type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never

  • 该类型可以获得函数的参数类型组成的元组类型
  • 示例:
function fpp(x: number): Array<number>{
  return [x]
}

type P = Parameters<typeof fpp> // [number]
  // 此时 P 的真实类型就是 fpp 的参数组成的元组类型 [number]

ThisType

/**
 * Marker for contextual 'this' type
 * 上下文“this”类型的标记
 */
interface ThisType<T> { }
  • 这个类型是用于指定上下文对象类型的。
  • 这类型怎么用呢,举个例子:
interface Cat {
    name: string;
    age: number;
}
const obj: ThisType<Person> = {
  mimi() {
    this.name // string
  }
}

这样, 可以指定 obj 里的所有方法里的上下文对象改成 Person 这个类型

// 没有ThisType情况下
const dog = {
    run() {
         console.log(this.age); // error,在dog中只有run一个函数,不存在a
    }
}
// 使用ThisType
const dog: { run: any } & ThisType<{ age: number }> = {
    run() {
         console.log(this.run) // error,因为没有在ThisType中定义
         console.log(this.age); // ok
    }
}
dog.run // ok 正常调用
dog.age // error,在外面的话,就跟ThisType没有关系了,这里就是没有定义age了

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 2 天 点击查看详情