TypeScript入门-高级类型 | 青训营笔记

98 阅读3分钟

这是我参与「第四届青训营 」笔记创作活动的第3天

TypeScript高级类型

除了 string、number、boolean 这种基础类型外,还有一些类型声明中的一些高级用法。

  • 交叉类型(&)
  • 联合类型(|)
  • 类型保护
  • null和undefined
  • 类型别名(type)
  • 类型索引(keyOf)
  • 类型约束(extends)

1. 联合/交叉类型

应用环境:类型声明繁琐,存在较多重复

interface:IHistoryBoook{
    author:string;
    type:string;
    range:string;
}
interface IStoryBook{
    author:string;
    type:string;
    theme:string;
}
type IBookList=Array<IHistoryBook|IStoryBook>;

联合类型:A|B;联合类型表示一个值可以是几种类型之一

交叉类型:A&B;多种类型叠加到一起成为一种类型,它包含了所需所有类型的特性

type IBookList=Array<{
    author:string;
}&({
    type:'history';
    range:string;
}|{
    type:'story';
    theme:string;
})>

2. 类型保护与类型守卫

类型守卫:定义一个函数,返回值是一个类型谓词,生效范围为子作用域

interface IA{a:1,a1:2}
interface IB{b:1,b1:2}
function getIsIA(arg:IA|IB):arg is IA{
    return !!(arg as IA).a;
}

function log2(arg:IA|IB){
    if(getIsIA(arg)){
        console.log(arg.a1);
    }else{
        console.log(arg.b1);
    }
}

联合类型+类型保护=自动类型推断

function logBook(book:IBookItem){
    if(book.type==='history'){
        console.log(book.range);
    }else{
        console.log(book.theme);
    }
}

3. 类型索引(keyOf)

keyof类似于Object.keys,用于获取一个接口中的key的联合类型

// 实现merge函数类型
function merge(sourceObj,targetObj){
    // 要求sourceObj必须为targetObj的子集
}
// 定义类型
interface ISourceObj{
    x?:string;
    y?:string;
}
interface ITargetObj{
    x:string;
    y:string;
}
// 函数类型
type IMerge=(sourceObj:ISourceObj,targetObj:ITargetObj)=>ITargetObj;
// 类型实现繁琐,若obj类型较为复杂,则source和targe需要大量重复
// 若target增加/减少key,则source需要联动去除

动态设置keyof

interface IMerge{
    <T extends Record<string,any>>(sourceObj:IPartial<T>,targetObj:T):T;
}
// Partial 是ts的内置工具泛型,将一个接口的所有属性设置为可选状态
type IPartial<T extends Record<string,any>>={
    [P in keyof T]?:T[P];
    // ? 通过设定对象可选选项,即自动推导出子集类型
}
type IKeys=keyof {a:string;b:number} ;
// type IKeys="a"|"b"
类型映射(inin用来做类型映射,遍历已有接口的key或者遍历联合类型

4. 类型约束 extends

extends主要用于对泛型加以约束,不像class使用extends是为了达到

 // demo
 type BaseType = string | number | boolean;
 
 public testGenerics<T extends BaseType>(arg: T): T {
   return arg;
 }
 this.testGenerics('123'); // 成功
 this.testGenerics({}); // 失败
extends的应用场景:extends 经常与 keyof 一起使用,例如我们有一个方法专门用来获取对象的值。
但是这个对象并不确定,我们就可以使用 extends 和 keyof 进行约束
// 根据传入的obj来约束key的值
function getValue<T, K extends keyof T>(obj: T, key: K) {
  return obj[key]
}

工具泛型

ts还内置了许多工具泛型

1. Partial

用于将一个接口的所有属性都设置成可选状态,执行步骤就是先通过keyof T,取出类型变量T的所有属性,然后通过in进行循环,最后在每个属性上加上?

 type Partial<T> = {
    [P in keyof T]?: T[P]
 }
 
 // demo
 interface ITeacher {
  age: number;
  gender: sex;
}

const teacherA: Partial<ITeacher> = {age: 28};  // 此时用了Partial,即使不给gender赋值也不会报错

2. Required

Required的作用刚好与Partial相反,是将接口中的每个属性都改成,主要区别就是将Partial中的?替换成-?

type Required<T> = {
    [P in keyof T]-?: T[P]
}

3. Exclude

Exclude的作用:如果 T 中的类型在 U 不存在,则返回,否则抛弃。

 type Exclude<T, U> = T extends U ? never : T
 
 //demo
 
 interface ITeacher {
  age: number;
  gender: sex;

}

interface IStudent {
  age: number;
  gender: sex;
  homeWork: string;
}

type ExcludeKeys = Exclude<keyof ITeacher, keyof IStudent>; // "homeWork"

4. Pick

Pick主要用于提取接口中的某几个属性

type Pick<T, K extends keyof T> = {
    [P in K]: T[P]
}

// demo
interface IStudent {
  name: string
  age: number
  homework: string
}

type StudentA = Pick<IStudent, "name" | "age"> // 提取这个接口中的name和age属性

const zs: StudentA = {
  name: 'zs',
  age: 18
}

5. Omit

Omit 的作用刚好和 Pick 相反, 用来排除接口中的某些属性

type Omit<T, K extends keyof any> = Pick<
  T, Exclude<keyof T, K>
>

// demo
interface IStudent {
  name: string
  age: number
  homework: string
}

type StudentA = Omit<IStudent, "name"> // 排除掉这个接口中的name属性

const zs: StudentA = {
  age: 18,
  homework: '背诵课文'
}