内置类型和类型断言


# 内置类型和类型断言

之前的基础类型标注、字面量类型与枚举、函数与 Class 等概念,在 JavaScript 中或多或少会有相关联的成分。但本文开始涉及到的知识点就是 TypeScript 独有的了。

# any

TypeScript 中提供了一个内置类型 any 表示「任意类型」:

// 显式 any
let anyVar: any;

// 隐式地推导 any
let foo;

// foo、bar 均为 any
function func(foo, bar){}
1
2
3
4
5
6
7
8

any 能兼容所有类型,也能够被所有类型兼容。但是滥用它的话就把 TypeScript 写成了大家调侃的 AnyScript 了。

为了避免这一情况,有以下使用小 tips:

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

# unknown

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

let unknownVar: unknown = "张三";

unknownVar = false;
unknownVar = "张三";
unknownVar = {
  name: "张三"
};

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;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

unknownany 的一个主要差异是 any 放弃了所有的类型检查,而 unknown 并没有。比如在对 unknown 类型的变量进行属性访问时:

let unknownVar: unknown;
unknownVar.foo(); // 报错:对象类型为 unknown
1
2

要对 unknown 类型进行属性访问,需要进行类型断言,即「虽然这是一个未知的类型,但我跟你保证它在这里就是这个类型」:

let unknownVar: unknown;

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

在类型未知的情况下,更推荐使用 unknown 标注。

# never

如果说 void 是一个空类型,那么 never 就更夸张了,它是真正的「什么都没有」类型。

nullundefined 一样,它是所有类型的子类型,但只有 never 类型的变量能够赋值给另一个 never 类型变量。

通常不会显式地声明一个 never 类型,但在某些情况下使用 never 确实是符合逻辑的,比如一个只负责抛出错误的函数:

function justThrow(): never {
  throw new Error()
}
1
2
3

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

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

function foo (input:number){
  if(input > 1){
    justThrow();
    // 等同于 return 语句后的代码,即 Dead Code
    const name = "张三";
  }
}
1
2
3
4
5
6
7
8
9
10
11

# 类型断言:警告编译器不准报错

类型断言能够显式告知类型检查程序当前这个变量的类型,其基本语法是 as NewType

比如将 any / unknown 类型断言到一个具体的类型:

let unknownVar: unknown;

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

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

需要注意的是,类型断言应当是在迫不得己的情况下使用的。虽然说我们可以用类型断言纠正不正确的类型分析,但类型分析在大部分场景下还是可以智能地满足我们需求的。

# 双重断言

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

const str: string = "apple";

// 从 X 类型 到 Y 类型的断言可能是错误的...
(str as { handler: () => {} }).handler()
1
2
3
4

此时编译器会提醒你先断言到 unknown 类型,再断言到预期类型,就像这样:

const str: string = "apple";

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

这是因为你的断言类型和原类型的差异太大,需要先断言到一个通用的类,即 any / unknown。这一通用类型包含了所有可能的类型,因此断言到它和从它断言到另一个类型差异不大。

# 非空断言

还有一种非空断言,即在值(变量、属性)的后边添加 ! 断言操作符,它可以用来排除值为 nullundefined 的情况:

let mayNullOrUndefinedOrString: null | undefined | string;
mayNullOrUndefinedOrString!.toString(); // ok
mayNullOrUndefinedOrString.toString();  // 报错
1
2
3

但应该尽量少用,因为无法保证之前一定非空的值,且这种错误只会在运行环境中抛出,静态类型检测是发现不了的。非空断言的替代方案是类型守卫(后面会讲)。

(完)