TypeScript 类型断言
一、类型断言
举个简单例子:
// 定义一个只能是 'a'/'b'/'c' 的类型typeT='a'|'b'|'c';// TS 推断 foo 的类型是 string(太宽泛了)letfoo='a';// 报错:string 类型不能赋值给 T 类型letbar:T=foo;这里 foo 明明是 ‘a’,但 TS 推断成了 string 类型(T 的父类型),父类型不能赋值给子类型,就报错了。
而类型断言就是用来“绕开”TS 的自动推断,手动指定值的类型——你比编译器更了解自己的代码,直接告诉它该怎么识别这个值!
改一下上面的例子,加上断言就不报错了:
typeT='a'|'b'|'c';letfoo='a';// 断言 foo 的类型是 T,TS 直接照做letbar:T=fooasT;// 正确!注意:类型断言不会真的改变值的类型,只是给编译器“提个醒”,运行时该是什么类型还是什么类型。
二、类型断言的两种写法(推荐第二种)
TS 提供了两种断言语法,功能完全一样,只是写法不同:
// 写法1:尖括号形式(和 JSX 语法冲突,不推荐)letbar:T=<T>foo;// 写法2:as 形式(推荐!无冲突,更易读)letbar:T=fooasT;因为 React 的 JSX 语法也用尖括号(比如<div>),为了避免混淆,现在都用值 as 类型的写法。
例子:操作 DOM 元素
日常开发中最常用的场景——获取输入框并读取 value 属性:
// TS 推断 username 的类型是 HTMLElement | nullconstusername=document.getElementById('username');if(username){// 断言成输入框类型,才能读取 value 属性(usernameasHTMLInputElement).value;// 正确!}这里的圆括号不能少,否则 TS 会把username as HTMLInputElement.value当成整体,直接报错
三、类型断言不可以随便使用
你不能把一个值断言成完全无关的类型,TS 会拦着你:
constn=1;// 报错:数值不能断言成字符串(完全无关)constm:string=nasstring;断言的规则:实际类型和断言类型必须“兼容”——要么是父子类型,要么是子父类型(比如 string 可以断言成 ‘a’|‘b’,反过来也可以)。
如果非要断言成完全无关的类型(不推荐),可以走“弯路”:先断言成unknown(所有类型的父类型),再断言成目标类型:
constn=1;// 先转 unknown,再转 string,不报错了constm:string=nasunknownasstring;四、超实用的特殊断言:as const
日常开发中,我们经常遇到“let 变量被推断成宽泛类型”的问题,比如:
lets='JavaScript';// 定义只能传这三个值的类型typeLang='JavaScript'|'TypeScript'|'Python';functionsetLang(language:Lang){}// 报错:string 类型不能传给 Lang 类型setLang(s);解决方法有两个:要么把 let 改成 const(TS 会推断成固定值类型),要么用as const断言:
// 断言后,s 的类型变成 'JavaScript'(不是 string 了)lets='JavaScript'asconst;setLang(s);// 正确!as const的核心作用:把值断言成“不可变的常量类型”,缩小 TS 的推断范围。
as const 还能用于对象/数组
// 普通声明:x/y 都是 number 类型constv1={x:1,y:2};// 断言整个对象:x=1、y=2,且只读constv2={x:1,y:2}asconst;// 普通数组:number[] 类型consta1=[1,2,3];// 断言后:只读元组 [1,2,3]consta2=[1,2,3]asconst;比如数组断言成元组后,传给固定参数的函数就不报错了:
functionadd(x:number,y:number){returnx+y;}constnums=[1,2]asconst;add(...nums);// 正确!五、非空断言:!(保证值不是 null/undefined)
如果一个值可能是 null/undefined,但你确定它一定有值,用!做非空断言:
// TS 担心 root 是 null,调用方法会报错constroot=document.getElementById('root');// root.addEventListener('click', () => {}); // 报错// 加 ! 断言:root 一定不是 nullconstroot=document.getElementById('root')!;root.addEventListener('click',()=>{});// 正确!非空断言别滥用!只有你确定值非空时才用,否则运行时可能报错。更稳妥的方式是手动判断:
constroot=document.getElementById('root');if(root){root.addEventListener('click',()=>{});}六、断言函数:确认参数类型
有时候我们需要写“校验函数”,确保参数符合类型要求,不符合就抛错——这就是断言函数:
// 新版写法:明确告诉 TS,这个函数断言 value 是字符串functionisString(value:unknown):assertsvalueisstring{if(typeofvalue!=='string'){thrownewError('不是字符串!');}}// 使用断言函数functiontoUpper(x:string|number){isString(x);// 执行后,TS 就知道 x 是字符串了returnx.toUpperCase();// 正确!}