TypeScript入门与实战
上QQ阅读APP看书,第一时间看更新

5.3.6 Nullable

TypeScript中的Nullable类型指的是值可以为undefined或null的类型。

JavaScript中有两个比较特殊的原始类型,即Undefined类型和Null类型。两者分别仅包含一个原始值,即undefined值和null值,它们通常用来表示某个值还未进行初始化。

在TypeScript早期的版本中,没有提供与JavaScript中Undefined类型和Null类型相对应的类型。TypeScript允许将undefined值和null值赋值给任何其他类型。虽然在TypeScript语言的内部实现中确实存在这两种原始类型,但是之前没有将它们开放给开发者使用。

TypeScript 2.0版本的一个改变就是增加了undefined类型和null类型供开发者使用。虽然看上去是一项普通的改进,但却有着非凡的意义。因为,不当地使用undefined值和null值是程序缺陷的主要来源之一,并有可能导致价值亿万美元的错误 Null类型的发明者Tony Hoare曾将Null描述为“billion-dollar mistake”。。相信一定有不少读者都曾经遇到过如下的JavaScript程序错误:


TypeError: Cannot read property 'xxx' of undefined

现在,在TypeScript程序中能够明确地指定某个值的类型是否为undefined类型或null类型。TypeScript编译器也能够对代码进行更加细致的检查以找出程序中潜在的错误。

5.3.6.1 undefined

undefined类型只包含一个可能值,即undefined值。undefined类型使用undefined关键字标识。示例如下:


01 const foo: undefined = undefined;

5.3.6.2 null

null类型只包含一个可能值,即null值。null类型使用null关键字标识。示例如下:


01 const foo: null = null;

5.3.6.3 --strictNullChecks

TypeScript 2.0还增加了一个新的编译选项“--strictNullChecks”,即严格的null检查模式。虽然该编译选项的名字中只提及了null,但实际上它同时作用于undefined类型和null类型的类型检查。

在默认情况下,“--strictNullChecks”编译选项没有被启用。这时候,除尾端类型外的所有类型都是Nullable类型。也就是说,除尾端类型外所有类型都能够接受undefined值和null值。关于尾端类型的详细介绍请参考5.8节。

例如,在没有启用“--strictNullChecks”编译选项时,允许将undefined值和null值赋值给string类型等其他类型。示例如下:


01 /**
02  * --strictNullChecks=false
03  */
04 let m1: boolean   = undefined;
05 let m2: string    = undefined;
06 let m3: number    = undefined;
07 let m4: bigint    = undefined;
08 let m5: symbol    = undefined;
09 let m6: undefined = undefined;
10 let m7: null      = undefined;
11 
12 let n1: boolean   = null;
13 let n2: string    = null;
14 let n3: number    = null;
15 let n4: bigint    = null;
16 let n5: symbol    = null;
17 let n6: undefined = null;
18 let n7: null      = null; 

该模式存在一个明显的问题,就是无法检查出空引用的错误。例如,已知某一个变量的类型是string,于是通过访问其length属性来获取该变量表示的字符串的长度。但如果string类型的变量值可以为undefined或null,那么这段代码在运行时将产生错误。示例如下:


01 /**
02  * --strictNullChecks=false
03  */
04 let foo: string = undefined; // 正确,可以通过类型检查
05 
06 foo.length;                  // 在运行时,将产生类型错误
07 
08 // 运行结果:
09 // Error: TypeError: Cannot read property 'length'
10 // of undefined

此例中,将undefined值赋值给string类型的变量foo时不会产生编译错误。但是,在运行时尝试读取undefined值的length属性将产生类型错误。这个问题可以通过启用“--strictNullChecks”编译选项来避免。

当启用了“--strictNullChecks”编译选项时,undefined值和null值不再能够赋值给不相关的类型。例如,undefined值和null值不允许赋值给string类型。在该模式下,undefined值只能够赋值给undefined类型;同理,null值也只能赋值给null类型。

还是以上例中的代码为例,如果我们启用了“--strictNullChecks”编译选项,那么TypeScript编译器就能够检查出代码中的错误。示例如下:


01 /**
02  * --strictNullChecks=true
03  */
04 let foo: string = undefined;
05 //  ~~~
06 //  编译错误!类型 'undefined' 不能赋值给类型 'string'
07 
08 foo.length;

此例第4行,TypeScript在执行静态类型检查时就能够发现这处类型错误,从而避免了在代码运行时才发现这个缺陷。

前面我们说在启用了“--strictNullChecks”编译选项时,undefined值只能够赋值给undefined类型,null值只能够赋值给null类型,实际上这种表述不完全准确。因为在该模式下,undefined值和null值允许赋值给顶端类型,同时undefined值也允许赋值给void类型。这些类型在后面的章节中会有详细介绍。示例如下:


01 /**
02  * --strictNullChecks=true
03  */
04 let m1: void = undefined;
05 
06 let m2: any     = undefined;
07 let m3: unknown = undefined;
08 
09 let n2: any     = null;
10 let n3: unknown = null;

undefined类型和null类型是不同的类型,它们必须被区分对待,不能互换使用。示例如下:


01 /**
02  * --strictNullChecks=true
03  */
04 const foo: undefined = null;
05 //    ~~~
06 //    编译错误!类型 'null' 不能赋值给类型 'undefined'
07 
08 const bar: null = undefined;
09 //    ~~~
10 //    编译错误!类型 'undefined' 不能赋值给类型 'null'

在了解了“--strictNullChecks”编译选项的作用后,让我们来看一看如何启用该编译选项。在默认情况下,“--strictNullChecks”编译选项没有被启用,我们需要在工程下的tsconfig.json配置文件中启用该编译选项,通过将“strictNullChecks”属性设置为“true”就能够启用“--strictNullChecks”编译选项。同理,如果将该属性设置为“false”则会关闭该编译选项。关于配置文件的详细介绍请参考8.3节。示例如下:


01 {
02     "compilerOptions": {
03         "strictNullChecks": true
04     }
05 }

如果读者使用的是TypeScript官网提供的在线代码编辑器,则可以在“Config”菜单中找到该选项并选中,即可启用该选项。

[1] Null类型的发明者Tony Hoare曾将Null描述为“billion-dollar mistake”。