Typescript之类型断言,类型守卫,自定义守卫(学习笔记)

1,331 阅读5分钟

类型断言

TS类型断言定义:把两种能有重叠关系的数据类型进行相互转换的一种 TS 语法,把其中的一种数据类型转换成另外一种数据类型。所谓重叠就是A包含B,或B包含A

以下几种都属于重叠关系:

  1. 如果 A,B 如果是类并且有继承关系

    【 extends 关系】无论 A,B 谁是父类或子类, A 的对象变量可以断言成 B 类型,B 的对象变量可以断言成A类型 。但注意一般在绝大多数场景下都是把父类的对象变量断言成子类。

class A {
  public name: string
  public age: number
}

class B extends A {
  public sex: string
}

let ins = new A()
let transferIns = ins as B
  1. 如果 A,B 如果是类,但没有继承关系

    两个类中的任意一个类的所有的 public 实例属性【不包括静态属性】加上所有的 public 实例方法和另一个类的所有 public 实例属性加上所有的 public 实例方法完全相同或是另外一个类的子集,则这两个类可以相互断言,否则这两个类就不能相互断言。

// A是B的子集
class A {
  public name: string
  public age: number
}

// B与重叠(B包含A)
class B {
  public name: string
  public age: number
  public sex: string
}

let ins = new A()
let transferIns = ins as B
let str = new B()
let transferStr = str as A

  1. 如果 B 是类,A 是接口,并且 B类实现了 A 接口【implements】,则 B的对象变量可以断言成 A 接口类型,同样 A 接口类型的对象变量也可以断言成B类型 。
interface A {
  name: string;
  age: number;
  eat():void
}

class B implements A{
  name: string;
  age: number;
  eat(): void {
    throw new Error("Method not implemented.");
  }
}

let ins = new B()
let transferIns = ins as A
let str: A = {
  name: "",
  age: 0,
  eat: function (): void {
    throw new Error("Function not implemented.");
  }
}
let transferStr = str as B
  1. 如果 B 是类,A 是接口,并且 B 类没有实现 A 接口,则断言关系和第2项的规则完全相同。
interface A {
  name: string;
  age: number;
}

class B {
  name: string;
  age: number;
  eat(): void {
    throw new Error("Method not implemented.");
  }
}

let ins = new B()
let transferIns = ins as A
let str: A = {
  name: "",
  age: 0,
  
}
let transferStr = str as B
  1. 如果 B 是类,A 是 type 定义的数据类型【就是引用数据类型,例如 Array, 对象,不能是基本数据类型,例如 string,number,boolean】,并且有 B 类实现了 A type 定义的数据类型【 implements】,则 B 的对象变量可以断言成 A type 定义的对象数据类型,同样 A type 定义的对象数据类型的对象变量也可以断言成 B 类型 。
type A = {
  name: string;
  age: number;
  eat():void
}

class B implements A{
  name: string;
  age: number;
  eat(): void {
    throw new Error("Method not implemented.");
  }
}

let ins = new B()
let transferIns = ins as A
let str: A = {
  name: "",
  age: 0,
  eat: function (): void {
    throw new Error("Function not implemented.");
  }
}
let transferStr = str as B
  1. 如果 B 是类,A 是 type 定义的数据类型,并且 B 类没有实现 A type定义的数据类型,则断言关系和第2项的规则完全相同。
type A = {
  name: string;
  age: number;
}

class B {
  name: string;
  age: number;
  eat(): void {
    throw new Error("Method not implemented.");
  }
}

let ins = new B()
let transferIns = ins as A
let str: A = {
  name: "",
  age: 0,
  
}
let transferStr = str as B
  1. 如果 B 是一个函数上参数变量的联合类型,例如 string |number,那么在函数内部可以断言成 string 或number 类型。不过这种情况一般我们会使用类型守卫而不会使用类型断言
function B(one: string | number) {
  this.one = (one as number) + 3
}

let ins = new B('5')

8.多个类组成的联合类型如何断言? 例如:let vechile: Car | Bus | Trunck。 vechile 可以断言成其中任意一种数据类型。 例如 vechile as Car, vechile as Bus , vechile as Trunck 。

  1. 任何数据类型都可以转换成 any 或 unknown 类型,any 或 unknown 类型也可以转换成任何其他数据类型。

类型守卫

类型守卫定义: 在语句的块级作用域【if语句内或条目运算符表达式内】缩小变量类型的一种类型推断的行为

类型守卫产生时机:TS条件语句中遇到下列条件关键字时,会在语句的块级作用域内缩小变量的类型,这种类型推断的行为称作类型守卫 ( Type Guard )。类型守卫可以帮助我们在块级作用域中获得更为需要的精确变量类型,从而减少不必要的类型断言。

  • 类型判断:typeof
  • 实例判断:instanceof
  • 字面量相等判断:==, ===, !=, !==

用法

1.typeof

function isType(input : unknown) {
  if(typeof input === 'number') {
      console.log('input为number类型', input.toFixed(2))
  }else if(typeof input === 'string') {
      console.log('input为string类型', input.slice(0))
  }
}
  1. instanceof
interface Padder {
  getPaddingString(): string
}

class SpaceRepeatingPadder implements Padder {
  constructor(private numSpaces: number) { }
  getPaddingString() {
      return Array(this.numSpaces + 1).join(" ");
  }
  getSpaceRepeating() {
    console.log('getSpaceRepeating')
  }
}

class StringPadder implements Padder {
  constructor(private value: string) { }
  getPaddingString() {
      return this.value;
  }
  getString() {
    console.log('String')
  }
}

function isType (ins: SpaceRepeatingPadder | StringPadder) {
  if(ins instanceof SpaceRepeatingPadder) {
    console.log('ins为SpaceRepeatingPadder类型',ins.getSpaceRepeating())
  }else if (ins instanceof StringPadder) {
    console.log('ins为StringPadder类型',ins.getString())
  }
}

isType(new SpaceRepeatingPadder(1))
isType(new StringPadder('1'))
  1. ==, ===, !=, !==
function isType(input : unknown) {
  if(input === 1) {
      console.log('input为number类型', input.toFixed(2))
  }else if(input === '1') {
      console.log('input为string类型', input.slice(0))
  }else if(input === true) {
      console.log('input为boolean类型',input)
  }else if(input) {
    console.log('只写input无法推断出类型')
  }else if(Object.prototype.toString.call(input).slice(8,-1) === "Object") {
    console.log('用Object.prototype.toString.call无法推断出类型')
  }
}

自定义类型守卫

自定义守卫格式:

function  函数名( 形参:参数类型【参数类型大多为any】)  : 形参 is A类型 =boolean+类型守卫能力{

	return  true or false

}

理解:返回布尔值的条件表达式赋予类型守卫的能力, 只有当函数返回 true 时,形参被确定为 A 类型

function isFunction(value: unknown): value is Function {
  return typeof value === 'function'
}
function isObject(value: unknown): value is object {
  return Object.prototype.toString.call(value) === '[object Object]'
}

function isString(value: unknown): value is string {
  return typeof value === 'string'
}


function B (input: unknown) {
  if(isFunction(input)) {
    console.log('input为Function类型', input())
  }else if(isObject(input)) {
    console.log('input为object类型', input.toString())
  }else if(isString(input)) {
    console.log('input为String类型', input.toLowerCase())
  }
}