TypeScript 学习笔记(四):类型守卫

类型守卫

  1. 类型守卫的作用在于触发类型缩小。实际上,它还可以用来区分类型集合中的不同成员

  2. 类型守卫包括switch、字面量恒等、typeof、instanceof、in 和自定义类型守卫 简单说当一个类型是多种可能时例如’any’,‘unknown’,‘联合类型’ 等在逻辑判断时候要具体到其唯一子集可能性

  • 2.1.类型判断:typeof
  • 2.2.实例判断:instanceof
  • 2.3.属性判断:in
  • 2.4.字面量相等判断:==, =, !=, !

1. 没有使用类型守卫案例

通过断言,断言其子类型做到缩小范围不报错,虽然这种形式也可以但是需要大量断言来区分类型

const getRandomValue = (list: (string | number)[]) => {
    
    
  const randomNumber = Math.random() * 10
  if (randomNumber > 5) return list[0]
  else return list[1]
}

const item = getRandomValue([1, 'w'])
// 直接这些写会报错
// 首先第一个问题是返回值的类型不确定是字符还是数字
// 数字是没有length 属性的所以这么写的判断ts 会报错的
// if(item.length){
    
    
//     console.log(item.length)
// }else{
    
    
//     console.log(item.toFixed())
// }

// 解决第一种使用类型断言
// 缺点 每一个item 都要使用对应的类型断言
if ((item as string).length) {
    
    
  console.log((item as string).length)
} else {
    
    
  console.log((item as number).toFixed())
}

2. 使用typeof 进行类型守卫

为什么用typeof 做类型守卫呢?因为typeof能判断JS基本数据类型。typeof只能识别以下类型:

  • Boolean
  • String
  • Undefined
  • Function
  • Number
  • Bigint
  • Symbol

写法: typeof a a 是变量(基本数据类型)

为什么 typeof 不能识别null呢?

let a= null
typeof a;//object

null 是一个只有一个值的特殊类型,表示一个空对象引用,可以用来清空对象,它是 object 类型是历史遗留下来的问题,曾提议改为 null 类型,被拒绝了。
typeof 识别其他的类型比如数组,正则等都是 object 类型

let a =[1]
typeof a;//Object
var reg = RegExp("a","i");
typeof reg//reg

typeof 怎么起到守卫的作用呢,通过 typeof 判断变量类型然后执行相应的逻辑,具体如下:

function getType(params: string | number) {
    
    
  // return params.length ? params.length : params 这么写会报错  这就是为什么要类型守卫缩小类型
  if (typeof params === 'string') {
    
    
    return params.length
  } else {
    
    
    return params
  }
}

上面案例的传参都会基本类型,当传一个对象时候,我们也可以用对象中的属性来进行判断,比如:

interface A{
    
    
   a:string;
}
interface B{
    
    
   a:number;
}
type Class = A | Bfunction getInfo(val:Class){
    
    
  //判断val的属性a的类型为number类型
   if(typeof val.a === "number"){
    
    
        console.log('B:'+ val.a)
   }
   //判断val的属性a的类型为string类型
   if(typeof val.a === "string"){
    
    
         console.log('A' + val.a)
   }
}

3. instanceof 针对类 – 缩小范围

为什么用 instanceof 呢?因为 typeof 有局限性,引用类型比如数组,正则等无法精确识别是哪一个种型, instanceof 能够识别变量(比如实例对象)是否属于这个类 instanceof 不能检测原始值类型的值,但是原始值对应的对象格式实例则可以检测。具体 instanceof 是怎么做类型守卫的呢?

写法: a instanceof b ,a是参数,b是一般都是接口类型。

interface Teacher{
    
    
   name:string;
   courses:string;
}
interface Student{
    
    
   name:string;
   study:string;
}
type Class = Teacher | Student;
function getInfo(val:Class){
    
    
  //判断val的类型是否是定义的接口Teacher类型
   if(val instanceof Teacher){
    
    
        console.log('teacher:'+ val.courses)
   }
   //判断val的类型是否是定义的接口Student类型
   if(val instanceof Student){
    
    
        console.log('student' + val.study)
   }
}

4. 获取value 值 – in

interface Teacher{
    
    
   name:string;
   courses:string;
}
interface Student{
    
    
   name:string;
   study:string;
}
type Class = Teacher | Student;
function getInfo(val:Class){
    
    
   //此时val类型缩小为Teacher类型
   if('courses' in val){
    
    
        console.log(val.courses)
   }
   //此时val类型缩小为Student类型
   if('study' in val){
    
    
        console.log(val.study)
   }
}
getInfo({
    
     name: 'student', study: "Philosophy" });
//打印结果为Philosophy,因为传参中含有study属性,所以走了第二个判断

注意:用 in 关键字缩小数据类型必须有一个独特的属性作为判别标准,否则不能用 in 关键字

5. 字面量恒等

1.如果是字面量这种联合类型可以使用具体值去比较
2.也可用switch 来缩小类型 代替if

function getType(params: 'a' | 1) {
    
    
  if (params === 'a') {
    
    
    return params.length
  }
  return params
}
type Foo = {
    
    
  kind: 'foo'; // 字面量类型
  foo: number;
};

type Bar = {
    
    
  kind: 'bar'; // 字面量类型
  bar: number;
};

function doStuff(arg: Foo | Bar) {
    
    
  if (arg.kind === 'foo') {
    
    
    console.log(arg.foo); // ok
    console.log(arg.bar); // Error
  } else {
    
    
    console.log(arg.foo); // Error
    console.log(arg.bar); // ok
  }
}

6. 自定义守卫 – is

TS中有一个关键字is 可以判断变量是否属于某种类型。

// 1.往往开发时候会吧一些判单同一封装 像下面的例子虽然封装了,但实际效果却报错

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

function getName(params: number | string) {
    
    
  // if(typeof params === "string") 可以的但是这种重复逻辑一般会封装
  if (isString(params)) {
    
    
    return params.length // 报错
  }  
}

正确写法:
1.这里的is 可以是任意类型,可以是自己定义的接口类型,这里是因为需求是string 类型,如果不做is 类型,
TS 只能推断出 isString 是一个返回布尔值的函数,而并不知道这个布尔值的具体含义’{形参} is {类型} 的语法结构’

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

function getName(params: number | string) {
    
    
  // if(typeof params === "string") 可以的但是这种重复逻辑一般会封装
  if (isString(params)) {
    
    
    return params.length 
  }  
}
1. 函数参数形式
interface Teacher{
    
    
   name:string;
   courses:string;
}
interface Student{
    
    
   name:string;
   study:string;
}
const isTeacher = function (cls: Teacher | Student): cls is Teacher {
    
    
    return 'courses' in cls;
}
const getName = (cls: Teacher | Student) => {
    
    
    if(isTeacher(cls)) {
    
    
        return cls.courses;
    }
}

2. this形式

下面代码中的 User 是抽象类,不能被实例化,Staff 和 Student 都继承自 User。实例方法 isStaff 用于将类型收窄为 Staff,实例方法 isStudent 用于将类型收窄为 Student

abstract class User {
    
    
    name: string;
    constructor(name: string) {
    
    
        this.name = name;
    }
    isStudent(): this is Student {
    
    
        return this instanceof Student;
    }
    isStaff(): this is Staff {
    
    
        return this instanceof Staff; 
    }
}

class Student extends User{
    
    
    study: string;
    constructor(name: string, study: string) {
    
    
        super(name)
        this.study = study
    }
}

class Staff extends User {
    
    
    workingYears: number;
    constructor(name: string, workingYears: number) {
    
    
        super(name)
        this.workingYears = workingYears
    }
}

function judgeClassType(obj: User) {
    
    
    if (obj.isStaff()) {
    
    
        // obj的类型被缩小为 Staff
    } else if (obj.isStudent()){
    
    
        // obj 的类型被缩小为 Student
    }
}

猜你喜欢

转载自blog.csdn.net/renlimin1/article/details/131680188