首页 前端知识 TypeScript基础之类型断言

TypeScript基础之类型断言

2024-04-29 11:04:53 前端知识 前端哥 313 246 我要收藏

前言

文中内容基本上参考https://ts.xcatliu.com/basics/type-assertion.html 。

类型断言

TypeScript中类型断言(Type Assertion)可以用来手动指定一个值的类型,用来覆盖TS中的推断。
当 TypeScript 确定赋值无效时,我们可以选择使用类型断言来覆盖类型。
注意:如果我们使用类型断言,赋值总是有效的,所以我们需要确保我们是正确的。否则,我们的程序可能无法正常运行。

两种执行类型断言的方法:

  • 使用角括号<类型>
  • as 关键字

基本使用

interface IPerson {
name: string;
age: number;
}
let person: IPerson = {
name: 'xman',
age: 18
} as IPerson
// 或者
let person1: IPerson = < IPerson > {
name: 'xman',
age: 18
}
复制

然而,当你在JSX中使用 <类型>值 的断言语法时,会产生语法冲突,因此建议使用 值 as 类型 的语法来类型断言。

常见的用途

类型断言的常见用途有以下几种:

将任何一个类型断言为any

当我们引用一个在某个对象上不存在的属性或方法时,就会报错:

let num: number = 1;
num.split(''); // Property 'split' does not exist on type 'number'.
复制

以上错误提示显然是非常有用的, 但是有的时候,我们可以非常确定这段代码不会出错,比如在window全局对象上加上某个属性:

window.foo = 1; // Property 'foo' does not exist on type 'Window & typeof globalThis'.
复制

TypeScript编译时会提示 window 上不存在foo属性,因此我们可以使用 as any临时将window断言为any类型:

(window as any).foo = 1;
复制

在 any 类型的变量上,访问任何属性都是允许的。
但是,使用any的同时,也需要它极有可能掩盖了真正的类型错误,所以尽量不要随意使用any。而且滥用类型断言可能会导致运行时错误。

let a: string = 'anything';
(a as any).setName('xman');
复制

如以上代码中,虽然使用类型断言后能通过编译,但却无法避免运行时的错误。
运行错误:Uncaught TypeError: a.setName is not a function

其实我们可以拓展window的类型来解决这个错误:

declare global {
interface Window {
foo: number
}
}
复制

只不过临时增加属性的话使用 as any会更加方便。

将一个联合类型断言为其中一个类型

当TypeScript不确定一个联合类型的变量到时是哪个类型的时候,我们只能访问此联合类型的所有类型中共有的属性或方法:

class Fish {
swim () {
console.log('游泳~');
}
eat () {
console.log('进食!');
}
}
class Bird {
fly () {
console.log('飞翔~');
}
eat () {
console.log('进食!');
}
}
function getSmallPet(): Fish | Bird {
return Math.random() > 0.5 ? new Fish() : new Bird()
}
let pet = getSmallPet();
pet.eat(); // 访问共有属性没毛病
function isFish (animal: Fish | Bird) {
// Property 'swim' does not exist on type 'Fish | Bird'.
// Property 'swim' does not exist on type 'Bird'.
if (typeof animal.swim === 'function') {
return true;
}
return false;
}
复制

此时可以使用类型断言, 将animal断言为Fish,来解决这个问题:

function isFish (animal: Fish | Bird) {
if (typeof (animal as Fish).swim === 'function') {
return true;
}
return false;
}
复制

将一个父类断言为更加具体的子类

当类之间有继承关系时,类型断言也是很常见的:

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中没有code属性,如果直接获取 error.code的话就会报错,所以我们需要使用类型断言获取(error as ApiError).code.
为什么不使用instanceof
如果ApiErrorHttpError不是一个类而是一个TS接口类型的话,它在编译后就会被删除,此时就无法使用instanceof来做运行时判断了

interface ApiError extends Error {
code: number;
}
interface HttpError extends Error {
statusCode: number;
}
function isApiError(error: Error) {
if (error instanceof ApiError) { // 'ApiError' only refers to a type, but is being used as a value here.
return true;
}
return false;
}
复制

编译后的结果:

"use strict";
function isApiError(error) {
if (error instanceof ApiError) { // 'ApiError' only refers to a type, but is being used as a value here.
return true;
}
return false;
}
复制

此时就只能使用类型断言,通过判断是否存在 code 属性,来判断传入的参数是不是 ApiError 了:

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类型断言为一个具体类型

比如有个函数,其返回值类型为any:

function getCacheData (key: string): any {
return (window as any).cache[key];
}
复制

那么我们在使用它时,最好能够将调用来它之后的返回值断言成一个精确的类型,以免后续操作出现问题:

interface Animal {
name: string;
eat(): void;
}
let bird = getCacheData('bird') as Animal;
bird.eat();
复制

在调用完getCacheData之后,立即断言为Animal类型,后续对bird的访问时就有了代码补全,这样就提高了代码的可维护性。

类型断言的限制

并不是任何一个类型都可以被断言为任何另一个类型。 先来看看TS中结构化类型系统的基本规则: 如果两个类型的结构一样,就说它们是互相兼容的,且可互相赋值(即如果类型x要兼容类型y, 那么类型y至少要具有与类型x相同的属性).

interface Animal {
name: string;
}
interface Fish {
name: string;
eat(): void;
}
let fish: Fish = {
name: '鲤鱼',
eat: () => {
console.log('eat~');
}
}
let animal: Animal = fish; // 没毛病
复制

以上代码中, Fish包含了 Animal 中的所有属性,此外,还有一个方法eat, TS只会检查Fish是否能赋值给Animal,编译器检查Animal中的每个属性,看是否能在Fish中也找到对应属性。而并不关心 Cat 和 Animal 之间定义时是什么关系。
所以以下代码成立:

interface Animal {
name: string;
}
interface Fish {
name: string;
eat(): void;
}
function testAnimal (animal: Animal) {
return (animal as Fish)
}
function testFish (fish: Fish) {
return (fish as Animal)
}
复制

总结: 若 A 兼容 B,那么 A 能够被断言为 B,B 也能被断言为 A。

同理,若 B 兼容 A,那么 A 能够被断言为 B,B 也能被断言为 A。

双重断言

使用as any as xxx
举个例子:

function getEvent (event: Event) {
let e = event || (window as any).event as Event;
}
复制

注意: 可以直接写window.event,写出(window as any).event as Event 只是举个例子而已。

非空断言

TypeScript2.0中提供的非空断言操作符(non-null-assertion-operator)
非空断言操作符操作符 ! 可以用于断言操作对象是非 null 和非 undefined 类型。即: x! 将从 x 值域中排除 nullundefined
如:

function handler (arg: string | null | undefined) {
let str: string = arg!; // 没毛病
str.split('');
// ...
}
复制

具体可看之前的文章 TypeScript基础之非空断言操作符、可选链运算符、空值合并运算符 - 掘金 (juejin.cn)

以上ts代码均在 https://www.typescriptlang.org/play 上运行过,版本为4.7.2。
最后, 如有错误,欢迎各位大佬指点!感谢!

参考资料

https://ts.xcatliu.com/basics/type-assertion.html

转载请注明出处或者链接地址:https://www.qianduange.cn//article/5983.html
标签
类型断言
评论
还可以输入200
共0条数据,当前/页
发布的文章

JQuery中的load()、$

2024-05-10 08:05:15

大家推荐的文章
会员中心 联系我 留言建议 回顶部
复制成功!