any,unknown,never,类型断言

214 阅读4分钟

内置类型:any 、unknown 与 never

有些时候,我们的 TS 代码并不需要十分精确严格的类型标注,为了能够表示“任意类型”,TypeScript 中提供了一个内置类型 any,来表示所谓的任意类型。

log(message?: any, ...optionalParams: any[]): void

any 类型的变量几乎无所不能,它可以在声明后再次接受任意类型的值,同时可以被赋值给任意其它类型的变量

// 被标记为 any 类型的变量可以拥有任意类型的值
let anyVar: any = "aaa";

anyVar = false;
anyVar = "aaa";
anyVar = {
  site: "bbb"
};

anyVar = () => { }

// 标记为具体类型的变量也可以接受任何 any 类型的值
const val1: string = anyVar;
const val2: number = anyVar;
const val3: () => {} = anyVar;
const val4: {} = anyVar;

any 类型的主要意义,其实就是为了表示一个无拘无束的“任意类型”,它能兼容所有类型,也能够被所有类型兼容

any使用规则

  • 如果是类型不兼容报错导致你使用 any,考虑用类型断言替代。
  • 如果是类型太复杂导致你不想全部声明而使用 any,考虑将这一处的类型去断言为你需要的最简类型。假设你需要调用 foo.bar.baz(),就可以先将 foo 断言为一个具有 bar 方法的类型。
  • 如果你是想表达一个未知类型,更合理的方式是使用 unknown

unknown

unknown 类型和 any 类型有些类似,一个 unknown 类型的变量可以再次赋值为任意其它类型,但只能赋值给 any 与 unknown 类型的变量

let unknownVar: unknown = "aaa";

unknownVar = false;
unknownVar = "aaa";
unknownVar = {
  site: "bbb"
};

unknownVar = () => { }

const val1: string = unknownVar; // Error
const val2: number = unknownVar; // Error
const val3: () => {} = unknownVar; // Error
const val4: {} = unknownVar; // Error

const val5: any = unknownVar;
const val6: unknown = unknownVar;

unknown 和 any 的一个主要差异体现在赋值给别的变量时,any可以赋值给别的类型,unknow只能给any和unknown


要对 unknown 类型进行属性访问,需要进行类型断言


never

内置类型 never 就是这么一个“什么都没有”的类型。此前我们已经了解了另一个“什么都没有”的类型,void。但相比于 void ,never 还要更加空白一些

type UnionWithNever = string | 599 | true | void | never;

将鼠标悬浮在类型别名之上,你会发现这里显示的类型是string | 599 | true | void。

never 类型被直接无视掉了,而 void 仍然存在。这是因为,void 作为类型表示一个空类型,就像没有返回值的函数使用 void 来作为返回值类型标注一样,void 类型就像 JavaScript 中的 null 一样代表“这里有类型,但是个空类型


通常我们不会显式地声明一个 never 类型,它主要被类型检查所使用。但在某些情况下使用 never 确实是符合逻辑的,比如一个只负责抛出错误的函数

function justThrow(): never {
  throw new Error()
}

一旦一个返回值类型为 never 的函数被调用,那么下方的代码都会被视为无效的代码(即无法执行到

function justThrow(): never {
  throw new Error()
}

function foo (input:number){
  if(input > 1){
    justThrow();
    // 等同于 return 语句后的代码,即 Dead Code
    let a = 1
  }
}

我们也可以显式利用它来进行类型检查,即上面在联合类型中 never 类型神秘消失的原因。假设,我们需要对一个联合类型的每个类型分支进行不同处理

declare const strOrNumOrBool: string | number | boolean;

if (typeof strOrNumOrBool === "string") {
  console.log("str!");
} else if (typeof strOrNumOrBool === "number") {
  console.log("num!");
} else if (typeof strOrNumOrBool === "boolean") {
  console.log("bool!");
} else {
  throw new Error(`Unknown input type: ${strOrNumOrBool}`);
}

类型断言

类型断言能够显式告知类型检查程序当前这个变量的类型

let unknownVar: unknown;

(unknownVar as { foo: () => {} }).foo();

可以 as 到 any 来为所欲为,跳过所有的类型检查(尽量不用)

可以在联合类型中断言一个具体的分支

function foo(union: string | number) {
  if ((union as string).includes("aaaa")) { }

  if ((union as number).toFixed() === '599') { }
}

** 类型断言的正确使用方式是,在 TypeScript 类型分析不正确或不符合预期时,将其断言为此处的正确类型**

非空断言

非空断言其实是类型断言的简化,它使用 ! 语法,即 obj!.func()!.prop 的形式标记前面的一个声明一定是非空的(实际上就是剔除了 null 和 undefined 类型

非空和可选

非空断言的运行时仍然会保持调用链,因此在运行时可能会报错。而可选链则会在某一个部分收到 undefined 或 null 时直接短路掉,不会再发生后面的调用

双重断言(了解即可,基本不会出现)

如果在使用类型断言时,原类型与断言类型之间差异过大,TypeScript 会给你一个类型报错。

const str: string = "linbudu";

(str as unknown as { handler: () => {} }).handler();

// 使用尖括号断言
(<{ handler: () => {} }>(<unknown>str)).handler();

类型层级

  • 最顶级的类型,any 与 unknown

  • 特殊的 Object ,它也包含了所有的类型,但和 Top Type 比还是差了一层

  • String、Boolean、Number 这些装箱类型

  • 原始类型与对象类型

  • 字面量类型,即更精确的原始类型与对象类型嘛,需要注意的是 null 和 undefined 并不是字面量类型的子类型

  • 最底层的 never