Skip to content

7.类型守卫(保护)

  • 类型守卫就是一些表达式,他们在编译的时候就能通过类型信息确保某个作用域内变量的类型
  • 类型守卫就是能够通过关键字判断出分支中的类型

0 为什么要用类型守卫

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

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

1 typeof 类型守卫

typescript
function double(input: string | number | boolean) {
    if (typeof input === 'string') {
        return input + input;
    } else {
        if (typeof input === 'number') {
            return input * 2;
        } else {
            return !input;
        }
    }
}

2 instanceof类型守卫

typescript
class Animal {
    name!: string;
}
class Bird extends Animal {
    swing!: number
}
function getName(animal: Animal) {
    if (animal instanceof Bird) {
        console.log(animal.swing);
    } else {
        console.log(animal.name);
    }
}

3 null守卫

  • 如果开启了strictNullChecks选项,那么对于可能为null的变量不能调用它上面的方法和属性
typescript
function getFirstLetter(s: string | null) {
    //第一种方式是加上null判断
    if (s == null) {
        return '';
    }
    //第二种处理是增加一个或的处理
    s = s || '';
    return s.charAt(0);
}
//它并不能处理一些复杂的判断,需要加非空断言操作符
function getFirstLetter2(s: string | null) {
    function log() {
        console.log(s!.trim()); // 非空断言
    }
    s = s || '';
    log();
    return s.charAt(0);
}

4 链判断运算符

  • 链判断运算符是一种先检查属性是否存在,再尝试访问该属性的运算符,其符号为 ?.
  • 如果运算符左侧的操作数 ?. 计算为 undefined 或 null,则表达式求值为 undefined 。否则,正常触发目标属性访问,方法或函数调用。
typescript
a?.b; // 如果a是null/undefined,那么返回undefined,否则返回a.b的值.
a == null ? undefined : a.b;

a?.[x]; // 如果a是null/undefined,那么返回undefined,否则返回a[x]的值
a == null ? undefined : a[x];

a?.b(); // 如果a是null/undefined,那么返回undefined
a == null ? undefined : a.b(); // 如果a.b不函数的话抛类型错误异常,否则计算a.b()的结果

a?.(); // 如果a是null/undefined,那么返回undefined
a == null ? undefined : a(); // 如果A不是函数会抛出类型错误
// 否则 调用a这个函数

5 可辨识的联合类型

  • 就是利用联合类型中的共有字段进行类型保护的一种技巧
  • 相同字段的不同取值就是可辨识
typescript
interface WarningButton{
  class:'warning',
  text1:'修改'
}
interface DangerButton{
  class:'danger',
  text2:'删除'
}
type Button = WarningButton | DangerButton;
function getButton(button: Button){
 if(button.class === 'warning'){
  console.log(button.text1);
 }
 if(button.class === 'danger'){
  console.log(button.text2);
 }
}

类型字面量+可辨识联合类型

typescript
interface User {
    username: string
}
type Action = {
    type:'add',
    payload:User
} | {
    type: 'delete'
    payload: number
}
const UserReducer = (action: Action) => {
  switch (action.type) {
    case "add":
      let user: User = action.payload;
      break;
    case "delete":
      let id: number = action.payload;
      break;
    default:
      break;
  }
};

6 in操作符

in 运算符可以被用于参数类型是否包含于指定的接口的判断

typescript
interface Bird {
    swing: number;
}

interface Dog {
    leg: number;
}

function getNumber(x: Bird | Dog) {
    if ("swing" in x) {
      return x.swing;
    }
    return x.leg;
}

7 自定义的类型守卫

  • TypeScript 里的类型保护本质上就是一些表达式,它们会在运行时检查类型信息,以确保在某个作用域里的类型是符合预期的
  • type is Type1Class就是类型谓词
  • 谓词为 parameterName is Type这种形式,parameterName必须是来自于当前函数签名里的一个参数名
  • 每当使用一些变量调用isType1时,如果原始类型兼容,TypeScript会将该变量缩小到该特定类型

语法:

bash
function 函数名(形参: 参数类型【参数类型大多为any】): 形参 is A类型 {
	return  true or false
}

例子:

typescript
// 自定义守卫1
function isType1(type: Type1Class | Type2Class): type is Type1Class {
    return (<Type1Class>type).func1 !== undefined;
}

interface Bird {
  swing: number;
}

interface Dog {
  leg: number;
}

// 自定义守卫2:没有相同字段可以定义一个类型保护函数
function isBird(x: Bird|Dog): x is Bird{
  return (<Bird>x).swing == 2;
  // return (x as Bird).swing == 2;
}

function getAnimal(x: Bird | Dog) {
  if (isBird(x)) {
    return x.swing;
  }
  return x.leg;
}

可以参考vue3中的isRef的守卫方法:core/packages/reactivity/src/ref.ts at 272ab9fbdcb1af0535108b9f888e80d612f9171d · vuejs/core (github.com)

8 unknown

  • TypeScript 3.0 引入了新的unknown 类型,它是 any 类型对应的安全类型
  • unknown 和 any 的主要区别是 unknown 类型会更加严格:在对 unknown 类型的值执行大多数操作之前,我们必须进行某种形式的检查。而在对 any 类型的值执行操作之前,我们不必进行任何检查

8.1 any 类型

  • 在 TypeScript 中,任何类型都可以被归为 any 类型。这让 any 类型成为了类型系统的 顶级类型 (也被称作 全局超级类型)。
  • TypeScript允许我们对 any 类型的值执行任何操作,而无需事先执行任何形式的检查
typescript
let value: any;

value = true;             // OK
value = 42;               // OK
value = "Hello World";    // OK
value = [];               // OK
value = {};               // OK
value = Math.random;      // OK
value = null;             // OK
value = undefined;        // OK


let value: any;
value.foo.bar;  // OK
value.trim();   // OK
value();        // OK
new value();    // OK

8.2 unknown 类型

  • 就像所有类型都可以被归为 any,所有类型也都可以被归为 unknown。这使得 unknown 成为 TypeScript 类型系统的另一种顶级类型(另一种是 any)
  • 任何类型都可以赋值给unknown类型
typescript
let value: unknown;

value = true;             // OK
value = 42;               // OK
value = "Hello World";    // OK
value = [];               // OK
value = {};               // OK
value = Math.random;      // OK
value = null;             // OK
value = undefined;        // OK
value = new TypeError();  // OK
  • unknown类型只能被赋值给any类型和unknown类型本身
typescript
let value: unknown;
let value1: unknown = value;   // OK
let value2: any = value;       // OK
let value3: boolean = value;   // Error
let value4: number = value;    // Error
let value5: string = value;    // Error
let value6: object = value;    // Error
let value7: any[] = value;     // Error
let value8: Function = value;  // Error

8.3 缩小 unknown 类型范围

  • 如果没有类型断言或类型细化时,不能在unknown上面进行任何操作
  • typeof
  • instanceof
  • 自定义类型保护函数
  • 可以对 unknown 类型使用类型断言
typescript
const value: unknown = "Hello World";
const someString: string = value as string;

8.4 联合类型中的 unknown 类型

  • 在联合类型中,unknown 类型会吸收任何类型。这就意味着如果任一组成类型是 unknown,联合类型也会相当于 unknown:

    typescript
    type UnionType1 = unknown | null;       // unknown
    type UnionType2 = unknown | undefined;  // unknown
    type UnionType3 = unknown | string;     // unknown
    type UnionType4 = unknown | number[];   // unknown

8.5 交叉类型中的 unknown 类型

  • 在交叉类型中,任何类型都可以吸收 unknown 类型。这意味着将任何类型与 unknown 相交不会改变结果类型
typescript
type IntersectionType1 = unknown & null;       // null
type IntersectionType2 = unknown & undefined;  // undefined
type IntersectionType3 = unknown & string;     // string
type IntersectionType4 = unknown & number[];   // number[]
type IntersectionType5 = unknown & any;        // any

8.6 never是unknown的子类型

typescript
type isNever = never extends unknown ? true : false;

8.7 keyof unknown 等于never

typescript
type key = keyof unknown;

8.8 只能对unknown进行等或不等操作,不能进行其它操作

typescript
un1 === un2;
un1 !== un2;
un1 += un2;

8.9 不能做任何操作

  • 不能访问属性
  • 不能作为函数调用
  • 不能当作类的构造函数不能创建实例
typescript
un.name
un();
new un();

8.10 泛型keyof映射属性

  • 如果映射类型遍历的时候是unknown,不会映射属性
typescript
type getType<T> = {
  [P in keyof T]:number
}
type t = getType<unknown>;

9 声明文件

当使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能

什么是声明语句

假如我们想使用第三方库 jQuery,一种常见的方式是在 html 中通过 <script> 标签引入 jQuery,然后就可以使用全局变量 $jQuery 了。

但是在 ts 中,编译器并不知道 $ 或 jQuery 是什么东西

typescript
/*
当使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能。
声明语句: 如果需要ts对新的语法进行检查, 需要要加载了对应的类型说明代码
  declare var jQuery: (selector: string) => any;
声明文件: 把声明语句放到一个单独的文件(jQuery.d.ts)中, ts会自动解析到项目中所有声明文件
下载声明文件: npm install @types/jquery --save-dev
*/

jQuery('#foo');
// ERROR: Cannot find name 'jQuery'.

这时,我们需要使用 declare var 来定义它的类型

typescript
declare var jQuery: (selector: string) => any

jQuery('#foo')

declare var 并没有真的定义一个变量,只是定义了全局变量 jQuery 的类型,仅仅会用于编译时的检查,在编译结果中会被删除。它编译结果是:

typescript
jQuery('#foo')

一般声明文件都会单独写成一个 xxx.d.ts 文件

创建 01_jQuery.d.ts,将声明语句定义其中,TS 编译器会扫描并加载项目中所有的 TS 声明文件

typescript
declare var jQuery: (selector: string) => any

很多的第三方库都定义了对应的声明文件库, 库文件名一般为 @types/xxx,可以在npm官网:https://www.npmjs.com/ 进行搜索

有的第三库在下载时就会自动下载对应的声明文件库(比如: webpack);有的可能需要单独下载(比如 jQuery/react)

10 内置对象

JavaScript 中有很多内置对象,它们可以直接在 TypeScript 中当做定义好了的类型。

内置对象是指根据标准在全局作用域(Global)上存在的对象。这里的标准是指 ECMAScript 和其他环境(比如 DOM)的标准。

  1. ECMAScript 的内置对象

Boolean Number String Date RegExp Error

typescript
/* 1. ECMAScript 的内置对象 */
let b: Boolean = new Boolean(1)
let n: Number = new Number(true)
let s: String = new String('abc')
let d: Date = new Date()
let r: RegExp = /^1/
let e: Error = new Error('error message')
b = true
// let bb: boolean = new Boolean(2)  // error

2.BOM 和 DOM 的内置对象

Window Document HTMLElement DocumentFragment Event NodeList

typescript
const div: HTMLElement = document.getElementById('test')
const divs: NodeList = document.querySelectorAll('div')
document.addEventListener('click', (event: MouseEvent) => {
  console.dir(event.target)
})
const fragment: DocumentFragment = document.createDocumentFragment()

Released under the MIT License.