类型断言能够显式告知类型检查程序当前这个变量的类型,可以进行类型分析地修正、类型。它其实就是一个将变量的已有类型更改为新指定类型的操作,它的基本语法是 as NewType,你可以将 any / unknown 类型断言到一个具体的类型:
let unknownVar: unknown;
(unknownVar as { foo: () => {} }).foo();
还可以 as 到 any 来为所欲为,跳过所有的类型检查:
const str: string = "linbudu";
(str as any).func().foo().prop;
也可以在联合类型中断言一个具体的分支:
function foo(union: string | number) {
if ((union as string).includes("linbudu")) { }
if ((union as number).toFixed() === '599') { }
}
但是类型断言的正确使用方式是,在 TypeScript 类型分析不正确或不符合预期时,将其断言为此处的正确类型:
interface IFoo {
name: string;
}
declare const obj: {
foo: IFoo
}
const {
foo = {} as IFoo
} = obj
这里从 {} 字面量类型断言为了 IFoo 类型,即为解构赋值默认值进行了预期的类型断言。当然,更严谨的方式应该是定义为 Partial
除了使用 as 语法以外,你也可以使用 <> 语法。它虽然书写更简洁,但效果一致,只是在 TSX 中尖括号断言并不能很好地被分析出来。你也可以通过 TypeScript ESLint 提供的 consistent-type-assertions 规则来约束断言风格。
需要注意的是,类型断言应当是在迫不得己的情况下使用的。虽然说我们可以用类型断言纠正不正确的类型分析,但类型分析在大部分场景下还是可以智能地满足我们需求的。
总的来说,在实际场景中,还是 as any 这一种操作更多。但这也是让你的代码编程 AnyScript 的罪魁祸首之一,请务必小心使用。
双重断言
如果在使用类型断言时,原类型与断言类型之间差异过大,也就是指鹿为马太过离谱,离谱到了指鹿为霸王龙的程度,TypeScript 会给你一个类型报错:
const str: string = "linbudu";
// 从 X 类型 到 Y 类型的断言可能是错误的,blabla
(str as { handler: () => {} }).handler()
此时它会提醒你先断言到 unknown 类型,再断言到预期类型,就像这样:
const str: string = "linbudu";
(str as unknown as { handler: () => {} }).handler();
// 使用尖括号断言
(<{ handler: () => {} }>(<unknown>str)).handler();
这是因为你的断言类型和原类型的差异太大,需要先断言到一个通用的类,即 any / unknown。这一通用类型包含了所有可能的类型,因此断言到它和从它断言到另一个类型差异不大。
非空断言
非空断言其实是类型断言的简化,它使用 ! 语法,即 obj!.func()!.prop 的形式标记前面的一个声明一定是非空的(实际上就是剔除了 null 和 undefined 类型),比如这个例子:
declare const foo: {
func?: () => ({
prop?: number | null;
})
};
foo.func().prop.toFixed();
此时,func 在 foo 中不一定存在,prop 在 func 调用结果中不一定存在,且可能为 null,我们就会收获两个类型报错。如果不管三七二十一地坚持调用,想要解决掉类型报错就可以使用非空断言:
foo.func!().prop!.toFixed();
其应用位置类似于可选链:
foo.func?.().prop?.toFixed();
但不同的是,非空断言的运行时仍然会保持调用链,因此在运行时可能会报错。而可选链则会在某一个部分收到 undefined 或 null 时直接短路掉,不会再发生后面的调用。
非空断言的常见场景还有 document.querySelector、Array.find 方法等:
const element = document.querySelector("#id")!;
const target = [1, 2, 3, 599].find(item => item === 599)!;
为什么说非空断言是类型断言的简写?因为上面的非空断言实际上等价于以下的类型断言操作:
((foo.func as () => ({
prop?: number;
}))().prop as number).toFixed();
怎么样,非空断言是不是简单多了?你可以通过 non-nullable-type-assertion-style 规则来检查代码中是否存在类型断言能够被简写为非空断言的情况。
类型断言还有一种用法是作为代码提示的辅助工具,比如对于以下这个稍微复杂的接口:
interface IStruct {
foo: string;
bar: {
barPropA: string;
barPropB: number;
barMethod: () => void;
baz: {
handler: () => Promise<void>;
};
};
}
假设你想要基于这个结构随便实现一个对象,你可能会使用类型标注:
const obj: IStruct = {};
这个时候等待你的是一堆类型报错,你必须规规矩矩地实现整个接口结构才可以。但如果使用类型断言,我们可以在保留类型提示的前提下,不那么完整地实现这个结构:
// 这个例子是不会报错的
const obj = <IStruct>{
bar: {
baz: {},
},
};
类型提示仍然存在:
在你错误地实现结构时仍然可以给到你报错信息: