如果我们有一个变量a
,它的类型是联合类型string | number
,我们希望调用slice
方法。但是slice
方法只存在于string
类型中。所以我们需要缩小a
的类型范围到string
才能使用slice
方法,否则会报错。这就是类型保护。
typeof
使用typeof
判断联合联合类型的变量:
function foo(a: string | number) {
// 'slice' does not exist on type 'string | number'
a.slice();
if (typeof a === "string") {
// 此时a的类型被推断为string,可以直接使用slice
a.slice();
}
}
instanceof
如果是类的联合类型,则可使用instanceof
:
class A {
a = "a";
}
class B {
b = "b";
}
function bar(ins: A | B) {
if (ins instanceof A) {
// 此时ins被推断为A
ins.a;
} else {
// 此时ins被推断为B
ins.b;
}
}
in
in
可以安全的检查一个对象是否具有某些属性:
interface First {
x: string;
}
interface Second {
y: number;
}
function baz(obj: First | Second) {
if ("x" in obj) {
// 此时obj被推断为First
obj.x;
} else {
// 此时obj被推断为Second
obj.y;
}
}
我们定义了两个接口,但是我们在做类型保护时,使用的是"x" in obj
,而不是使用obj instanceof First
。
instanceof
后需要跟一个变量。而接口只是一个类型。接口类型只是用于编译时的类型约束,而编译后的代码中不会存在任何接口相关的代码。类则不同(是一个类型,也是一个变量),除了在编译时做类型约束,也会存在于编译后的代码中。
自定义保护类型
除了上述的方法,我们还可以创建自定义的保护类型函数,它的返回值形如:parameterName is Type
:
// 还是使用in代码示例中的First和Second两个接口
function isFirst(p: First | Second): p is First {
// 暂时将p断言为First类型,尝试访问x属性,判断它是否存在
return (p as First).x !== undefined;
}
其实该函数的返回值本质就是一个布尔值,但是我们使用p is First
来让这个布尔值具有类型保护的作用,实际使用时:
function doWhat(p: First | Second) {
if (isFirst(p)) {
// 此时p被推断为First
p.x;
} else {
// 此时p被推断为Second
p.y;
}
}
在3.7版本中中还增加了assertion functions,也可以用来做类型保护:
function assert(condition: any, msg?: string): asserts condition {
if (!condition) {
throw msg;
}
}
其中condition
是一个条件判断,assert
函数的返回值是asserts condition
。用于在condition
为true
时,缩小类型范围:
function doSomething(p: any) {
assert(typeof p === "string");
// 此时的p为string
p.slice;
}
使用condition
的形式对复杂类型的判断还是无能为力:
function doSomething(p: First | Second) {
assert((val as First).x !== undefined);
// 无法正确缩小范围,此时p还是First | Second
p;
}
assert
函数形式还有一种形式可以对复杂类型进行判断:
function assertVal(val: any, msg?: string): asserts val is string {
if (typeof val === "string") {
throw msg;
}
}
function doSomething(p: any) {
assertVal(p);
// 此时的p为string
p.slice;
}
这种形式就和之前自定义保护类型的p is First
十分相似,如果判断复杂类型:
function assertVal(val: any, msg?: string): asserts val is First {
if ((val as First).x !== undefined) {
throw msg;
}
}
function doSomething(p:any) {
assertVal(p);
// 此时p能把正确推断为First类型
p;
}
可选链
在3.7新添加了可选链,让我们能够更安全的深层获取对象的属性,使用?.
操作符。
let obj: {
bar?: {
baz?: any;
};
} = {};
let x = obj?.bar?.baz;
一旦某一层为undefined
或者为null
,则最终的结果为undefined
,否则就可以取到实际的值。试想如果没有可选链,我们必须层层判断:
let x =
obj === null || obj === undefined
? undefined
: obj.bar === null || obj.bar === undefined
? undefined
: obj.bar.baz;
实际上,ts对可选链的编译结果与之类似:
var _a;
var obj;
var x = (_a = obj === null || obj === void 0 ? void 0 : obj.bar) === null || _a === void 0 ? void 0 : _a.baz;
与可选链类似还有一种非空断言操作符!.
,它表示开发者自己确定该变量不是undefined
或者null
,如:
function makeSure(a?: string) {
polyfill();
// 此时a被推断为 string | undefined
a.slice;
function polyfill() {
if (a === undefined) {
a = "default";
}
}
}
虽然我们提前使用了polyfill
保证了a
变量肯定是一个string
类型的变量,但ts对这种深层嵌套的改变并没有识别到,所以它还是推断为string | undefined
,此时我们就可以用!.
:
a!.slice; // 告诉ts,我确定a不是一个undefined,你别在说a可能是undefined了
需要注意的是:!.
只是在编译时做了一个类型说明,但是并没有像可选链一样做类型保护,比如可选链的例子替换为!.
:let x = obj!.bar!.baz
,最终编译的结果:
var x = obj.bar.baz;