在即将发布的 Angular V9 中,开发者可以在创建项目时通过添加 --strict 标签为项目增加更多类型检查机制。
ng new demoproject --strict
更完备的类型检查内容可以帮助开发更轻松的管控项目质量,减少意外。
启用严格模式后的项目的编译配置会增加如下内容:
noImplicitAny
在 typescript 中,如果你没有参数进行显示注释,typescript将会隐式地将参数标记为 any 类型,并执行后续的操作,虽然对 JavaScript 开发者而言,这是很常见的行为方式,但是缺乏类型检查可能会增加出现问题时 debug 的难度。
开启 noImplicitAny 后
typescript 将不再允许对参数的隐式 Any
function log(someArg) { // Error : someArg has an implicit `any` type
sendDataToServer(someArg);
}
需要使用显式注释才可以通过编译
function log(someArg: number) {
sendDataToServer(someArg);
}
如果确信不需要类型检查,则需要将参数显示注释为 any
function log(someArg: any) {
sendDataToServer(someArg);
}
noImplicitReturns
确保每一个函数(包含函数中每一个逻辑分支)都都有显式的返回值(除非函数显式地将返回结果注释为 void)
getTotal(discount: number): number {
if (discount) {
const priceWithoutDiscount = this.product.unitPrice * this.quantity;
const discountAmount = priceWithoutDiscount * discount;
return priceWithoutDiscount - discountAmount;
} else {
// We forgot about this branch!
}}
在使用 noImplicitReturns 标签的情况下,上述代码无法通过编译。
noImplicitThis
this 的错误用法,运行下述代码
class Rectangle {
constructor(w, h) {
this.w = w;
this.h = h;
}
getAreaFunction() {
return function() {
return this.w * this.h;
};
}
}
let rectangle = new Rectangle(2, 5);
let areaFunction = rectangle.getAreaFunction();
let area = areaFunction();
console.log(area);
// this-wrong-use.ts
结果为
Cannot read property 'w' of undefined
上述代码中 areaFunction 被调用时,this 并没有指向 Rectangle 的实例。
事实上在运行时,this 的值依赖于函数被调用的方式:
- 通过对象实例调用 obj.func()
- 直接调用 func()
使用严格模式后,如果函数通过 func() 的方式直接调用,this 的值为 undefined
使用 noImplicitThis 后,typescript 会警告有关 this 的错误使用,效果如下
$ tsc --noImplicitThis this-wrong-use.ts
this-wrong-use.ts:12:19 - error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation.
12 return this.w * this.h;
~~~~
this-wrong-use.ts:12:28 - error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation.
12 return this.w * this.h;
noFallthroughCasesInSwitch
使用 noFallthroughCasesInSwitch 进行 switch 声明校验。
请看如下功能代码:
enum SwitchEnum {
ONE,
TWO
}
function testEnumSwitch(value: SwitchEnum) : string {
let returnValue = "";
switch(value) {
case SwitchEnum.ONE:
returnValue = "One";
case SwitchEnum.TWO:
returnValue = "Two";
}
return returnValue;
}
上述代码中,定义了一个名为 SwitchEnum 的 enum 类型数据;定义了一个名为 testEnumSwitch 的函数,接受一个 SwitchEnum 类型的参数 value,并返回 string 类型的结果。testEnumSwitch 函数中定义了一个名为 returnValue 的字符串类型变量并初始化为空字符串,使用 switch 声明逻辑将 returnValue 的值设置为 ”One“ 或 ”Two“,并最终将其作为结果返回。
上述代码在使用 noFallthroughCasesInSwitch 的情况下,会报出如下错误
error TS7029: Fallthrough case in switch.
这代表着 typescript 识别出了上述代码中 switch 代码段的错误。
假设,上述函数接受的入参为 SwitchEnum.ONE, 那么按照期待的功能,returnValue 的值应当被设置为 ”One“ 并返回。但事实却是,swtich statement 的逻辑将会继续执行到第二个case,而将 returnValue 设置为 ”Two“。事实上,无论传入的参数内容几何,最终函数的返回结果都将是 ”two“。
正确的实现代码如下:
function testEnumSwitch(value: SwitchEnum) : string {
let returnValue = "";
switch(value) {
case SwitchEnum.ONE:
returnValue = "One";
break;
case SwitchEnum.TWO:
returnValue = "Two";
}
return returnValue;
}
通过在 case 后增加 break 的方式避免 switch 逻辑 fall through。
借助于 noFallthroughCasesInSwitch ,typescript 编译器正确地指出了 switch 语句中存在明显错误,避免了 switch statement logic falls through 的问题。
strictNullChecks
严格的 Null 校验。
在严格的 Null 校验模式下,null
和 undefined
只能用于特定类型的变量(只能用于自身和 any 类型的变量) 。
在常规的类型检查模式中,T
和 T
| undefined
可以被当做是同一个含义的泛型 (undefined 可以被视为 T 的子类型);但是在严格的 NULL 校验模式下,只有 T
| undefined
才可以接受 undefined 的参数。
同样的规则也适用于 T
| null
// Compiled with --strictNullChecks
let x: number;
let y: number | undefined;
let z: number | null | undefined;
x = 1; // Ok
y = 1; // Ok
z = 1; // Ok
x = undefined; // Error
y = undefined; // Ok
z = undefined; // Ok
x = null; // Error
y = null; // Error
z = null; // Ok
x = y; // Error
x = z; // Error
y = x; // Ok
y = z; // Error
z = x; // Ok
z = y; // Ok
Assigned-before-use checking
在严格的 Null 校验模式下,编译器要求在每一个可能的代码路径中,对不包括 undefined 类型的局部变量,每次引用之前都需要明确赋值。
// Compiled with --strictNullChecks
let x: number;
let y: number | null;
let z: number | undefined;
x; // Error, reference not preceded by assignment
y; // Error, reference not preceded by assignment
z; // Ok
x = 1;
y = null;
x; // Ok
y; // Ok
Optional parameters and properties
对于可选的参数/属性而言,即使你没有显示地要求对其进行 undefined
类型声明, 其类型会自动添加 undefined
。
// Compiled with --strictNullChecks
type T1 = (x?: number) => string; // x has type number | undefined
type T2 = (x?: number | undefined) => string; // x has type number | undefined
Non-null and non-undefined type guards
如果对象或函数的类型包含 null 或 undefined ,则访问属性或函数调用可能会产生编译错误。目前,type guards
已扩展为支持 Non-null 和 non-undefined 校验。
// Compiled with --strictNullChecks
declare function f(x: number): string;
let x: number | null | undefined;
if (x) {
f(x); // Ok, type of x is number here
}
else {
f(x); // Error, type of x is number? here
}
let a = x != null ? f(x) : ""; // Type of a is string
let b = x && f(x); // Type of b is string | 0 | null | undefined
Non-null and non-undefined 类型校验必须使用 ==
,!=
,===
, 或!==
操作符与 null 和 undefined 进行比较。