Typescript 进阶篇二

最近使用 vue3+typescript 开发的新项目,让我对 TypeScript 有个一个更新的认识。TypeScript 发展至今,已经成为大型项目的标配,其提供的静态类型系统,大大增强了代码的可读性以及可维护性: 能够更早的发现代码中的错误、支持 JavaScript 语言的最新特性以及相同的语法和语义、跨平台。本系列博客会将我对 TypeScript 学习做一个回顾和总结。

类型守卫

类型守卫是一类特殊形式的表达式,具有特定的代码编写模式。编译器能够根据已知的模式从代码中识别出这些类型守卫表达式,然后分析类型守卫表达式的值,从而能够将相关的变量、参数或属性等的类型细化为更加具体的类型。

typeof 类型守卫 (类型判断)

typeof 运算符用于获取操作数的数据类型。typeof 运算符的返回值是一个字符串,该字符串表明了操作数的数据类型。

操作类型对应返回值

操作数的类型 typeof 返回值
Undefined 类型 "undefined"
Null 类型 "object"
Boolean 类型 "boolean"
Number 类型 "number"
String 类型 "string"
BigInt 类型 "bigint"
Function 类型 "function"
Object 类型 "object"
function getWidth(width: string | number) {
  if (typeof width === "number") {
    //类型为number
    return width + "px";
  }
  if (typeof width === "string") {
    //类型为string
    return width;
  }
  throw new Error(`Expected string or number, got '${width}'.`);
}
复制代码

instanceof 类型守卫 (实例判断)

instanceof 运算符能够检测实例对象与构造函数之间的关系。instanceof 运算符的左操作数为实例对象,右操作数为构造函数,若构造函数的 prototype 属性值存在于实例对象的原型链上,则返回 true;否则,返回 false。

//自定义构造函数
class Circle {
  static str = "Hello World"; //静态属性
  _radius: number = 4; //实例属性
}
class Square {
  width: number = 4; //实例属性
}
class Shape extends Circle {
  type = "circle";
}
const mkCircle = new Circle();
const mkShape = new Shape();

console.log(mkCircle instanceof Circle);  //true
console.log(mkShape instanceof Circle); //true  子类继承了父类实例
console.log(mkShape instanceof Square); //false;Shape构造函数的 prototype 属性值存在于Square实例对象的原型链

复制代码

in 类型守卫 (属性判断)

in 运算符用来判断对象自身或其原型链中是否存在给定的属性,若存在则返回 true,否则返回 false

interface Circle {
  description: string;
  radius: number;
}
interface Square {
  description: string;
  width: number;
}

function test(x: Circle | Square) {
  if ("radius" in x) {
    console.log("Circle类型");
  } else if ("width" in x) {
    console.log("Square类型");
  }
}
test({ description: "圆形", radius: 4 });  //Circle类型
复制代码

索引类型

通过索引类型查询能够获取给定类型中的属性名类型。索引类型查询的结果是由字符串字面量类型构成的联合类型,该联合类型中的每个字符串字面量类型都表示一个属性名类型

索引类型查询 keyof T

  • 表示类型 T 所有公共属性的字面量的联合类型
interface Circle {
  description: string;
  radius: number;
}
type circleKey = keyof Circle;  //等同于type circleKey = "description" | "radius";
let a: circleKey = "description";
let b: circleKey = "radius";
let c: circleKey = "width";  //编译失败; Type '"width"' is not assignable to type 'circleKey'.
复制代码
  • 类型 T 中包含字符串索引签名,则 keyof 的结果类型为 string 类型和 number 类型的联合类型
//keyof Circle 的类型等同于 string|number
interface Circle {
  [prop: string]: boolean;
}
type circleKey = keyof Circle;
let a: circleKey = 1;
let b: circleKey = "hello";
复制代码
  • 类型 T 中包含数值索引签名,则 keyof 的结果类型为 number 类型
//keyof Circle 的类型等同于 number
interface Circle {
  [prop: number]: boolean;
}
type circleKey = keyof Circle;
let a: circleKey = 1;
复制代码
  • 类型 T 中包含其他属性成员,那么将表示属性名的字符串字面量类型和数字字面量类型添加到结果类型 KeyofT
interface Circle {
  0: "circle";
  radius: number;
  getRadius(): void;
}
type circleKey = keyof Circle; //等同于 0|"radius"|"getRadius"
let a: circleKey = 0;
let b: circleKey = "radius";
let c: circleKey = "getRadius";
复制代码
  • 如果查询的类型为联合类型,那么先计算联合类型的结果类型,再执行索引类型查询
type A = {
  a: string;
  z: boolean;
};
type B = {
  b: string;
  z: boolean;
};
type AB = keyof (A | B); // 等同于 "z"
let a: AB = "z";
复制代码
  • 如果查询的类型为交叉类型,那么会将原索引类型查询展开为子索引类型查询的联合类型,展开的规则类似于数学中的“乘法分配律”
type A = {
  a: string;
  z: boolean;
};
type B = {
  b: string;
  z: boolean;
};
type AB = keyof (A & B); // 等同于 "a"|"b"|"c"
let a: AB = "a";
let b: AB = "b";
let c: AB = "c";
复制代码

索引访问类型 T[K]

索引访问类型能够获取对象类型中属性成员的类型

type A = {
  a: string;
  z: boolean;
};
type tpA = A["a"];

let a: A["a"] = "hello";
let z: A["z"] = false;
复制代码

映射对象类型 {[P in K]:T}

映射对象类型是一种独特的对象类型,它能够将已有的对象类型映射为新的对象类型;K 遍历的结果类型将作为对象属性名类型,因此类型 K 必须能够赋值给联合类型“string | number | symbol”

  • 若当前遍历出来的类型成员 P 为字面量类型,则在结果对象类型中创建一个新的属性成员,属性名类型为该字面量类型且属性值类型为 T
const s = Symbol()
type K = "a" | 1 | typeof s
type MapA = {
   [P in K]: boolean;
}
//等同于
type MapB = {
  a:boolean;  //字符串字面量类型
  1:boolean;  //数字字面量类型
  [s]:boolean;  //“unique symbol”类型
}
let a: MapA = {"a":false,1:false,[s]:true};
复制代码
  • 若当前遍历出来的类型成员 P 为 string 类型,则在结果对象类型中创建字符串索引签名
type mapA = {
  [P in string]: number;
};
//等同于
type mapB = {
  [propName: string]: number;
};
let a: mapA = { a: 1, b: 2 };
let b: mapB = { a: 1, b: 2 };
复制代码
  • 若当前遍历出来的类型成员 P 为 number 类型,则在结果对象类型中创建数值索引签名
type mapA = {
  [P in number]: boolean;
};
//等同于
type mapB = {
  [propName: number]: boolean;
};
let a: mapA = { 1: true, 2: false };
let b: mapB = { 1: true, 2: false };
复制代码

添加/删除修饰符

同态映射对象类型的修饰符都无法删除属性已有的修饰符,例如 :

type mapA = {
  a?: string;
  readonly z: boolean;
};
//等同于,包括?和readonly
type mapB = {
  [P in keyof mapA]: mapA[P];
};
let a: mapB = { z: false };
a.z = true; //编译报错 Cannot assign to 'z' because it is a read-only property.
复制代码

为了能更灵活的使用映射对象类型,TS 引入了两个新的修饰符用来精确控制添加或移除映射属性的“?”修饰符和 readonly 修饰

  • “+”修饰符,为映射属性添加“?”修饰符或 readonly 修饰符
  • “-”修饰符,为映射属性一出“?”修饰符或 readonly 修饰符
type mapA = {
  a?: string;
  readonly z: boolean;
};

type mapB = {
  -readonly [P in keyof mapA]-?: mapA[P];  //删除readonly和?修饰符
};
//等同于
type mapC = {
  a: string;
  z: boolean;
};
let a: mapB = { a:"hello",z: false };
复制代码

条件类型

定义 : T extends U ? X : Y ;如果类型 T 能够赋值给类型 U,则条件类型的结果为类型 X,否则为 Y

type mapA = true extends boolean?string:number //条件类型结果为string
let a:mapA = "a"
复制代码

分布式条件类型

如果类型 T 为联合类型,则分布式条件类型结果为由子条件类型构成的联合类型

type typeA<T> = T extends string?string:T extends number?number:T extends boolean?:boolean:[]
type typeB = typeA<string|number> //类型结果为 string | number

//相当于
type typeC = typeA<string> | typeA<number>
复制代码

过滤联合类型

  • 联合类型中 U=U0|U1,如果 U1 是 U0 的子类型,那么联合类型可以化简为“U = U0”
  • never 类型是任意其他类型的子类型,

结合联合类型和 never 的特性,我们可以从联合类型中过滤特定类型

type typeA = null | undefined | string | number;
type typeB<T> = T extends null | undefined ? never : T; //相当于 string|number|never,简化为string|number,过滤掉了null、undefine
type typeC = typeB<typeA>
let a:typeC = "a"
let b:typeC = 1
let c:typeC = null  //编译出错
let d:typeC = undefined   //编译出错

复制代码

infer 关键字 (T extends infer U ? U:Y)

在 extends 语句中类型 U 的位置上允许使用 infer 关键字来定义可推断的类型变量,可推断的类型变量只允许在条件类型的 true 分支使用

//案例1, 推断类型为string
type typeA<T> = T extends (infer R)[] ? R : never;
type typeR = typeA<string[]>; // string

//案例2 推断类型为string | boolean
type mapA<T> = T extends { a: infer U; b: infer U } ? U : never;
type mapT = mapA<{ a: boolean; b: string; c: string }>; // string | boolean
let b: mapT = false;
let c: mapT = "a";

//案例3 推断类型为string | boolean
type ParameterA<T extends (...args: any) => any> = T extends (
  ...args: infer P
) => any
  ? P
  : never;
type typeP = ParameterA<(a: string, b: boolean) => any>;
let a: typeP = ["a", false];

复制代码

内置工具

Partial

构造一个新类型,并将实际类型参数 T 中的所有属性变为可选属性

//源码
type Partial<T> = {
    [P in keyof T]?: T[P];
};

复制代码

Required

构造一个新类型,并将实际类型参数 T 中的所有属性变为必选属性

//源码
type Required<T> = {
    [P in keyof T]-?: T[P];
};
复制代码

Readonly

构造一个新类型,并将实际类型参数 T 中的所有属性变为只读属性

//源码
type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};
复制代码

Record<K, T>

使用给定的对象属性名类型和对象属性类型创建一个新的对象类型

//源码
type Record<K extends keyof any, T> = {
    [P in K]: T;
};

//案例1
type mapKeys = "name" | "sex";
//Record<mapKeys, string> 类型结果为 {name:string;sex:string}
let a: Record<mapKeys, string> = {
  name: "moonKing",
  sex: "女",
};

//案例2  结合keyof使用
type mapB = { a: string; b: string };
//Record<keyof mapB, number> 类型结果为 {a:number;b:number}
let b: Record<keyof mapB, number> = {
  a: 1,
  b: 2,
};

复制代码

Pick<T, K>

从已有对象类型中选取给定的属性及其类型,然后构建出一个新的对象类型

//源码
type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};
//案例
type mapA = { a: string; b: string };
type typeA = Pick<mapA, "a">; //类型结果为{ a: string}
let a: typeA = { a: "a" };
复制代码

Omit<T, K>

从已有对象类型中剔除给定的属性,然后构建出一个新的对象类

//源码
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

//案例
type mapA = { a: string; b: string };
type typeA = Omit<mapA, "a">; //类型结果为{ b: string}
let a: typeA = { b: "b" };

复制代码

Exclude<T, U>

从类型 T 中剔除所有可以赋值给类型 U 的类型

//源码
type Exclude<T, U> = T extends U ? never : T;

//案例
type typeA = null | undefined | string | number;
type typeB = Exclude<typeA, null | undefined>; //类型结果为string | number
let a: typeB = 1;
复制代码

Extract<T, U>

从类型 T 中获取所有可以赋值给类型 U 的类型

//源码
type Extract<T, U> = T extends U ? T : never;

//案例
type typeA = null | undefined | string | number;
type typeB = Extract<typeA, string | number>; //类型结果为string | number
let a: typeB = 1;

复制代码

NonNullable

从类型 T 中剔除 null 类型和 undefined 类型并构造一个新类型,也就是获取类型 T 中的非空类型

//源码
type NonNullable<T> = T extends null | undefined ? never : T;
复制代码

Parameters

获取函数类型 T 的参数类型并使用参数类型构造一个元组类型

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

//案例
type typeA = (id: number,sex:string) => string | undefined;
type typeB = Parameters<typeA>; //类型结果为[number,string]
let a:typeB = [1,""]

复制代码

ConstructorParameters

获取构造函数 T 中的参数类型,并使用参数类型构造一个元组类型

//源码
type ConstructorParameters<T extends new (...args: any) => any> = T extends new (...args: infer P) => any ? P : never;

//案例
type typeA = new (id: number, sex: string) => any
type typeB = ConstructorParameters<typeA>; //类型结果为[number,string]
let a: typeB = [1, ""];

复制代码

ReturnType

获取函数类型 T 的返回值类型

//源码
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;

//案例
type typeA = (id: number, sex: string) => number[];
type typeB = ReturnType<typeA>; //类型结果为number[]
let a: typeB = [1,2,3];
复制代码

InstanceType

能够获取构造函数的返回值类型,即实例类型

//源码
type InstanceType<T extends new (...args: any) => any> = T extends new (...args: any) => infer R ? R : any;

复制代码

最后

码字不易,来个小心心哦!

猜你喜欢

转载自juejin.im/post/7037131997638033416