Appearance
类型断言
const app: HTMLElement | null = document.getElementById('#app') 非空断言,一定有值,TS 语法 app!.style.color = 'red'
双重断言,不建议使用,会破坏原有类型。 (app as any) as boolean
语法
typescript
// 类型断言有两种形式。 其一是“尖括号”语法:
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
// 另一个为as语法:
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
// 两种形式是等价的。 至于使用哪个大多数情况下是凭个人喜好;然而,当你在TypeScript里使用JSX时,只有 as语法断言是被允许的。用途
将一个联合类型断言为其中一个类型
typescript
// 有时候,我们需要在还不确定类型的时候就访问其中一个类型特有的属性或方法,比如:
interface Cat {
name: string;
run(): void;
}
interface Fish {
name: string;
swim(): void;
}
function isFish(animal: Cat | Fish) {
if (typeof animal.swim === 'function') { // Error
return true;
}
return false;
}
// 可以使用类型断言,将 animal 断言成 Fish:这样就可以解决访问 animal.swim 时报错的问题了。
// 需要注意的是,类型断言只能够「欺骗」TypeScript 编译器,无法避免运行时的错误,反而滥用类型断言可能会导致运行时错误:
function isFish(animal: Cat | Fish) {
if (typeof (animal as Fish).swim === 'function') {
return true;
}
return false;
}将一个父类断言为更加具体的子类
当类之间有继承关系时,类型断言也是很常见的:
typescript
class ApiError extends Error {
code: number = 0;
}
class HttpError extends Error {
statusCode: number = 200;
}
function isApiError(error: Error) {
if (typeof (error as ApiError).code === 'number') {
return true;
}
return false;
}上面的例子中,我们声明了函数 isApiError,它用来判断传入的参数是不是 ApiError 类型,为了实现这样一个函数,它的参数的类型肯定得是比较抽象的父类 Error,这样的话这个函数就能接受 Error 或它的子类作为参数了。
但是由于父类 Error 中没有 code 属性,故直接获取 error.code 会报错,需要使用类型断言获取 (error as ApiError).code。
大家可能会注意到,在这个例子中有一个更合适的方式来判断是不是 ApiError,那就是使用 instanceof:
typescript
class ApiError extends Error {
code: number = 0;
}
class HttpError extends Error {
statusCode: number = 200;
}
function isApiError(error: Error) {
if (error instanceof ApiError) {
return true;
}
return false;
}上面的例子中,确实使用 instanceof 更加合适,因为 ApiError 是一个 JavaScript 的类,能够通过 instanceof 来判断 error 是否是它的实例。
但是有的情况下 ApiError 和 HttpError 不是一个真正的类,而只是一个 TypeScript 的接口(interface),接口是一个类型,不是一个真正的值,它在编译结果中会被删除,当然就无法使用 instanceof 来做运行时判断了:
typescript
interface ApiError extends Error {
code: number;
}
interface HttpError extends Error {
statusCode: number;
}
function isApiError(error: Error) {
if (error instanceof ApiError) { // Error
return true;
}
return false;
}此时就只能用类型断言,通过判断是否存在 code 属性,来判断传入的参数是不是 ApiError 了:
typescript
interface ApiError extends Error {
code: number;
}
interface HttpError extends Error {
statusCode: number;
}
function isApiError(error: Error) {
if (typeof (error as ApiError).code === 'number') {
return true;
}
return false;
}将任何一个类型断言为any
typescript
// 有的时候,我们非常确定这段代码不会出错
window.foo = 1; // Error
// 我们可以使用 as any 临时将 window 断言为 any 类型:
(window as any).foo = 1;
// 在 any 类型的变量上,访问任何属性都是允许的。
// 需要注意的是,将一个变量断言为 any 可以说是解决 TypeScript 中类型问题的最后一个手段。
// 它极有可能掩盖了真正的类型错误,所以如果不是非常确定,就不要使用 as any。
// 上面的例子中,我们也可以通过扩展 window 的类型解决这个错误,不过如果只是临时的增加 foo 属性,as any 会更加方便。
// 总之,一方面不能滥用 as any,另一方面也不要完全否定它的作用,我们需要在类型的严格性和开发的便利性之间掌握平衡(这也是 TypeScript 的设计理念之一),才能发挥出 TypeScript 最大的价值。将any断言为一个具体的类型
在日常的开发中,我们不可避免的需要处理 any 类型的变量,它们可能是由于第三方库未能定义好自己的类型,也有可能是历史遗留的或其他人编写的烂代码,还可能是受到 TypeScript 类型系统的限制而无法精确定义类型的场景。 遇到 any 类型的变量时,我们可以选择无视它,任由它滋生更多的 any。 我们也可以选择改进它,通过类型断言及时的把 any 断言为精确的类型,亡羊补牢,使我们的代码向着高可维护性的目标发展。 举例来说,历史遗留的代码中有个 getCacheData,它的返回值是 any:
typescript
function getCacheData(key: string): any {
return (window as any).cache[key];
}那么我们在使用它时,最好能够将调用了它之后的返回值断言成一个精确的类型,这样就方便了后续的操作:
typescript
function getCacheData(key: string): any {
return (window as any).cache[key];
}
interface Cat {
name: string;
run(): void;
}
const tom = getCacheData('tom') as Cat;
tom.run();
// 调用完 getCacheData 之后,立即将它断言为 Cat 类型。这样的话明确了 tom 的类型,后续对 tom 的访问时就有了代码补全,提高了代码的可维护性。类型断言的限制
若 A 兼容 B,那么 A 能够被断言为 B,B 也能被断言为 A。
typescript
interface Animal {
name: string;
}
interface Cat {
name: string;
run(): void;
}
let tom: Cat = {
name: 'Tom',
run: () => { console.log('run') }
};
let animal: Animal = tom;