类型断言
TypeScript 允许你覆盖它的推断,并且能以你任何你想要的方式分析它,这种机制被称为「类型断言」。TypeScript 类型断言用来告诉编译器你比它更了解这个类型,并且它不应该再发出错误。
类型断言的一个常见用例是当你从 JavaScript 迁移到 TypeScript 时:
const foo = {};
foo.bar = 123; // Error: 'bar' 属性不存在于 ‘{}’
foo.bas = 'hello'; // Error: 'bas' 属性不存在于 '{}'
这里的代码发出了错误警告,因为 foo
的类型推断为 {}
,即没有属性的对象。因此,你不能在它的属性上添加 bar
或 bas
,你可以通过类型断言来避免此问题:
interface Foo {
bar: number;
bas: string;
}
const foo = {} as Foo;
foo.bar = 123;
foo.bas = 'hello';
as foo
与 <foo>
最初的断言语法如下所示:
let foo: any;
let bar = <string>foo; // 现在 bar 的类型是 'string'
然而,当你在 JSX 中使用 <foo>
的断言语法时,这会与 JSX 的语法存在歧义:
let foo = <string>bar;</string>;
因此,为了一致性,我们建议你使用 as foo
的语法来为类型断言。
类型断言与类型转换
它之所以不被称为「类型转换」,是因为转换通常意味着某种运行时的支持。但是,类型断言纯粹是一个编译时语法,同时,它也是一种为编译器提供关于如何分析代码的方法。
类型断言被认为是有害的
在很多情景下,断言能让你更容易的从遗留项目中迁移(甚至将其他代码粘贴复制到你的项目中),然而,你应该小心谨慎的使用断言。让我们用最初的代码作为示例,如果你没有按约定添加属性,TypeScript 编译器并不会对此发出错误警告:
interface Foo {
bar: number;
bas: string;
}
const foo = {} as Foo;
// ahhh, 忘记了什么?
另外一个常见的想法是使用类型断言来提供代码的提示:
interface Foo {
bar: number;
bas: string;
}
const foo = <Foo>{
// 编译器将会提供关于 Foo 属性的代码提示
// 但是开发人员也很容易忘记添加所有的属性
// 同样,如果 Foo 被重构,这段代码也可能被破坏(例如,一个新的属性被添加)。
};
这也会存在一个同样的问题,如果你忘记了某个属性,编译器同样也不会发出错误警告。使用一种更好的方式:
interface Foo {
bar: number;
bas: string;
}
const foo: Foo = {
// 编译器将会提供 Foo 属性的代码提示
};
在某些情景下,你可能需要创建一个临时的变量,但至少,你不会使用一个承诺(可能是假的),而是依靠类型推断来检查你的代码。
双重断言
类型断言,尽管我们已经证明了它并不是那么安全,但它也还是有用武之地。如下一个非常实用的例子所示,当使用者了解传入参数更具体的类型时,类型断言能按预期工作:
function handler(event: Event) {
const mouseEvent = event as MouseEvent;
}
然而,如下例子中的代码将会报错,尽管使用者已经使用了类型断言:
function handler(event: Event) {
const element = event as HTMLElement; // Error: 'Event' 和 'HTMLElement' 中的任何一个都不能赋值给另外一个
}
如果你仍然想使用那个类型,你可以使用双重断言。首先断言成兼容所有类型的 any
,编译器将不会报错:
function handler(event: Event) {
const element = (event as any) as HTMLElement; // ok
}
TypeScript 是怎么确定单个断言是否足够
当 S
类型是 T
类型的子集,或者 T
类型是 S
类型的子集时,S
能被成功断言成 T
。这是为了在进行类型断言时提供额外的安全性,完全毫无根据的断言是危险的,如果你想这么做,你可以使用 any
。