TypeScript 类型断言

在前面的学习中,我们已经深刻感受到了 TypeScript 极其严苛的类型检查。只要类型不匹配,编辑器就会立马飘红报错。

但在实际开发中,偶尔也会遇到一种非常尴尬的情况:作为开发者,我们明明在逻辑上非常确定这个变量是什么类型,但 TypeScript 编译器偏偏就是推断不出来,并且死活要给你报错。

这个时候,我们就需要用一种方式来强行告诉编译器:“别猜了,听我的,我知道它是什么类型!” 这种机制,在 TypeScript 中也被称为 “类型断言(Type Assertion)”。

TypeScript 类型断言是什么?

类型断言好比其他语言里的 “类型转换”,但它不会在运行时进行任何特殊的数据重构。它纯粹是一个只在 “编译阶段” 起作用的语法,用于明确告知 TypeScript 编译器。

在 TypeScript 中,类型断言主要有两种语法:尖括号语法和 as 语法。

语法:

// 使用尖括号
const 变量名 = <类型>值;

// 使用 as 关键字
const 变量名 = 值 as 类型;

说明:

在现代前端开发(比如 React 的 JSX 语法、或 Vue 的 TSX)中,尖括号这种写法很容易和 HTML 标签发生冲突。因此,在真实项目开发中,我们都是推荐使用 as 关键字来进行类型断言。

示例 1:对象的类型断言

interface User {
    name: string;
    age: number;
}

// 类型断言
const currentUser = {} as User;

// 再去动态补充属性
currentUser.name = "Jack";
currentUser.age = 20;

console.log(currentUser.name);

运行结果如下。

Jack

分析:

对于上面这个例子,我们不使用类型断言,而是写成下面这样,则 TypeScript 是会拦截报错的。这是因为此时 TypeScript 会认为这是一个空对象,然后你又没有提供 name 和 age 属性。

interface User {
    name: string;
    age: number;
}

// 没有使用类型断言
const currentUser: User = {};

currentUser.name = "Jack";
currentUser.age = 20;

console.log(currentUser.name);

在某些业务场景下(比如逐步构建一个庞大的配置对象),我们可能无法在声明对象的的一开始就提供所有的必填属性。但是通过使用 as User,等于告诉 TypeScript 编译器并承诺稍后会把它补全。

注意: 使用了类型断言之后,这其实是由你主动承担了类型安全的责任。

DOM 元素的类型断言

很多小伙伴可能会觉得上面的例子还能通过其他方式避免。那类型断言究竟在什么场景下是非用不可的呢?最经典、最高频的场景就是:操作 DOM 元素。

假设 HTML 页面中有一个 <img id="logo" /> 标签。

示例 2:DOM 操作中的困境

// 1. 常规获取元素
const oLogo = document.getElementById("logo");

// 2. 尝试修改图片的 src 属性
oLogo.src = "logo.png";
// 报错:类型 “HTMLElement” 上不存在属性 “src”。

分析:

为什么会报错呢?结合我们前面学过的知识,这里其实存在着以下 2 个问题:

  • 严格模式下,TypeScript 无法确定页面上一定存在这个元素,所以 document.getElementById() 的返回值可能是 null。
  • 就算元素存在,TypeScript 也只知道它是个基础的 HTMLElement(普通 HTML 元素),并不知道它具体是个 div、span 还是 img,所以不允许你操作 img 专属的 src 属性。

面对这种双重困境,此时我们就可以使用 as 类型断言了。

示例 3:断言为具体的图片元素

// 类型断言为 HTMLImageElement
const oLogo = document.getElementById("logo") as HTMLImageElement;

// 此时 TypeScript 知道它有 src 属性,不仅不报错,还会给你代码提示
oLogo.src = "logo.png";

TypeScript 双重断言

虽然类型断言可以让我们 “指鹿为马”,但 TypeScript 并没有完全丧失理智。如果我们试图把两个完全八竿子打不着的类型互相断言,它依然会阻止你。

示例 4:互相冲突的断言

const count: number = 100;

// 试图把数字直接断言成字符串
const str = count as string; 
// 报错:类型 "number" 到类型 "string" 的转换可能是错误的,因为两种类型不能充分重叠。

分析:

TypeScript 会认为数字和字符串差异太大,直接转换太过于荒谬。但如果碰到了极端恶心的老旧遗留代码,非得强行转换不可,我们可以使用传说中的 “双重断言” 大法:先将其断言为 unknown 或 any,再断言为目标类型

示例 5:使用双重断言强行转换

const count: number = 100;

// 双重断言
const str = count as unknown as string;

console.log(typeof str);

运行结果如下。

number

分析:

不过需要注意的是,双重断言是非常危险的,我们只建议在对接毫无类型规范的第三方远古库时作为保底手段使用。在正常的业务代码中,绝对要杜绝 “双重断言” 的出现(非常重要)。

TypeScript 非空断言(!)

在实际开发中,还有一种特殊的断言场景:当我们开启了 TypeScript 的 “严格空值检查” 后,有时候编译器会提示某个值可能是 null 或 undefined。

如果我们非常确信这个值在运行时绝对不可能是空,此时可以使用 “非空断言符(!)”。

示例 6:使用非空断言符

function formatName(name: string | undefined) {
    // 强行断言 name 不是空值
    const cleanName = name!.trim();
    console.log("处理后的名字:", cleanName);
}

const myName: string = "  Jack  ";
formatName(myName);

运行结果如下。

处理后的名字:Jack

分析:

在这个例子中,formatName() 函数的参数可能是一个字符串,也可能是 undefined。如果我们直接调用 name.trim(),则 TypeScript 会报错拦截。

但我们在变量后面加上 “!”,强行断言它绝对不是空值,那么 TypeScript 编译器就不会报错了。

TypeScript 常量断言(as const)

在现代 Vue 或 React 的大型项目中,还有一个出场率极高的特殊断言:as const(常量断言)。

我们都知道,使用 const 声明的对象,其内部的属性依然是可以被修改的。如果我们想要定义一个绝对不可篡改的全局配置对象,或者一个固定的权限列表,就可以使用 as const。

提示: 如果小伙伴们对 const 的底层不够了解,另请参阅:JavaScript let 和 const

示例 7:使用常量断言

// 普通声明
const config = {
    apiUrl: "https://api.lvyenet.com",
    timeout: 5000
};
config.timeout = 10000;    // 完全合法,正常修改

// 使用常量断言
const secureConfig = {
    apiUrl: "https://api.lvyenet.com",
    timeout: 5000
} as const;

// secureConfig.timeout = 10000; 
// 报错:无法为“timeout”赋值,因为它是只读属性。

分析:

当我们在对象或数组的末尾加上 as const 后,TypeScript 会在底层做两件非常强大的事情:

  • 它会把这个对象内部的 “所有层级” 的属性都变成 readonly(只读),彻底锁死,杜绝任何篡改。
  • 它会把属性的类型从宽泛的 string 或 number,直接收窄为具体的 “字面量类型”(比如类型直接变成了 "https://api.lvyenet.com" 和 5000)。

最后需要说明的是,不管是 “as”(类型断言),还是 “!”(非空断言),它们本质上都是开发者对编译器的一种 “强行干预”。

作为一名优秀的工程师,正确的做法是:我们应该优先依靠 TypeScript 强大的类型推断和联合类型来保证代码安全。只有在编译器因为环境限制而真的 “变傻” 时(如 DOM 操作),才考虑使用 “类型断言”。

给站长反馈

绿叶网正在不断完善中,小伙伴们如果发现任何问题,还望多多给站长反馈,谢谢!

邮箱:lvyenet@vip.qq.com

「绿叶网」服务号
绿叶网服务号放大
关注服务号,微信也能看教程。
绿叶网服务号