阅读本文你能学到什么?
- 类型断言是什么,as 怎么用
- 泛型是什么,为什么需要它
- 泛型接口、泛型别名、泛型函数的基本用法
- 泛型约束的作用与用法
前言
第四篇我们搞定了字面量类型和类型推论。但实际项目中,还有两个绕不开的问题:你比 TS 更清楚某个值的类型,怎么告诉它“相信我”?又或者,写一个函数,它能接收任意类型,又能保留传入的类型信息——怎么让类型跟着参数走?
第一个问题的答案是类型断言,第二个是泛型。
这一篇,咱们就把这两个技能一起搞定。
一、类型断言
1.1 什么是类型断言?
类型断言是告诉 TypeScript 编译器“我知道这个值是什么类型,你按我说的来”。它不会在运行时改变值,只是让 TS 通过编译。
下面通过两个常见场景来理解它的用法。
示例一:DOM 元素获取
比如document.getElementById('username'),TypeScript 只知道它返回的是HTMLElement | null——它只知道页面上有个元素叫username,但不知道它具体是什么标签,可能是input,也可能是div。
但作为开发者,你清楚这个username对应的就是一个输入框:
constusernameInput=document.getElementById('username')asHTMLInputElement// 现在 TS 知道它是 HTMLInputElement,可以放心访问 .valueconsole.log(usernameInput.value)// ✅示例二:处理unknown类型
unknown类型的值不能直接使用,需要用断言告诉 TS 它到底是什么类型。
letvalue:unknown='Hello'// value.length // ❌ 报错:value 是 unknownletlen=(valueasstring).length// ✅ 断言为 string 后可以访问1.2 语法:推荐使用as
TypeScript 有两种断言语法:<类型>值和值 as 类型。在.tsx文件中,尖括号语法会与 JSX 标签产生解析冲突,所以推荐统一使用as。
// ❌ 不推荐:尖括号语法(在 JSX 中会冲突)letvalue=<string>'hello'// ✅ 推荐:as 语法letvalue='hello'asstring1.3as const断言
除了用as指定类型,TypeScript 还提供了as const,用于控制类型推断的精度——它告诉 TypeScript 把类型推断为最精确的字面量类型。
// 没有 as constletcolor='red'// 推断为 string// 有 as constletcolor='red'asconst// 推断为 'red'// 对象的 as constconstconfig={url:'/api',method:'GET'}asconst// 推断为:{ readonly url: '/api'; readonly method: 'GET' }as const在需要精确字面量类型的场景非常有用,比如定义路由配置、常量对象等。
1.4 类型断言的注意事项
类型断言只能够“欺骗”TypeScript 编译器,无法避免运行时的错误。滥用类型断言可能会导致运行时错误。
functionprocessValue(value:string|number){// 假设我们断言 value 是 numberconstnum=valueasnumberconsole.log(num.toFixed(2))}processValue('hello')// ❌ 编译通过,但运行时报错:num.toFixed is not a function💡 给新手的提醒:类型断言应该在你比 TS 更清楚类型的时候使用,而不是用来绕过类型检查。如果你不确定值的类型,建议先用
typeof或instanceof在代码里做判断,而不是直接用断言。
二、泛型
2.1 什么是泛型?为什么需要它?
泛型(Generics)是指在定义接口、函数等类型的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。使用泛型可以复用类型并且让类型更加灵活。
思考一下:下面的两种数据结构,如何使用interface接口实现类型注解?这样做有什么问题?
// 用户数据interfaceUserData{code:numbermessage:stringdata:{id:number;name:string}}// 商品数据interfaceProductData{code:numbermessage:stringdata:{id:number;name:string;price:number}}我们可以对比下数据结构,这两个接口的结构完全一样,只有data的类型不同。如果每种数据类型都写一个接口,代码会变得非常冗余——泛型就是来解决这个问题的。
使用泛型后,只需要定义一次:
interfaceResponseData<T>{code:numbermessage:stringdata:T}2.2 泛型接口
在接口interface后面使用<T>声明泛型参数,接口里的其他成员都可以使用该参数的类型。
语法:interface 接口名<T> { ... }
// 定义泛型接口interfaceResponseData<T>{code:numbermessage:stringdata:T}// 使用泛型接口interfaceUser{id:numbername:string}interfaceProduct{id:numbername:stringprice:number}constuserResponse:ResponseData<User>={code:200,message:'获取用户成功',data:{id:1,name:'张三'}}constproductResponse:ResponseData<Product>={code:200,message:'获取商品成功',data:{id:1001,name:'手机',price:2999}}我们可以把泛型理解成一个函数——
T就像类型的“参数”,可以传一个,也可以传多个,比如ResponseData<T>、ResponseData<T, U>。
2.3 泛型别名
在类型别名type的后面使用<T>声明泛型参数,类型别名里的其他成员都可以使用该参数的类型。
语法:type 类型别名<T> = { ... }
// 定义泛型别名typeResponseData<T>={code:numbermessage:stringdata:T}typeUser={id:numbername:string}constresponse:ResponseData<User>={code:200,message:'获取用户成功',data:{id:1,name:'张三'}}2.4 泛型函数
在函数名称的后面使用<T>声明泛型参数,整个函数中(参数、返回值、函数体)都可以使用该参数的类型。
语法:function 函数名<T>(参数: T): T { ... }
// 需求:创建一个 createArray 函数// 它可以创建一个指定长度的数组,同时将每一项都填充一个默认值(多种类型)functioncreateArray<T>(length:number,defaultValue:T):T[]{constarr=newArray(length)for(leti=0;i<length;i++){arr[i]=defaultValue}returnarr}// 使用示例conststrArray=createArray<string>(3,'hello')// ['hello', 'hello', 'hello']constnumArray=createArray<number>(3,100)// [100, 100, 100]constboolArray=createArray<boolean>(3,true)// [true, true, true]// 也可以让 TS 自动推断类型constinferredArray=createArray(3,'hello')// TS 自动推断为 string[]2.5 泛型约束
泛型的特点就是灵活不确定,有些时候泛型函数的内部需要访问一些特定类型的数据才有的属性,此时会有类型错误。
functiongetLength<T>(value:T):number{// ❌ 报错:T 类型上不一定有 length 属性returnvalue.length}解决方案:通过泛型约束来解决——使用extends关键字让传入的类型必须满足某个条件。
语法:T extends 类型—— 限制T必须是某个类型的子类型。
// 定义一个有 length 属性的接口interfaceLength{length:number}// 约束:T 必须具有 length 属性functiongetLength<TextendsLength>(value:T):number{returnvalue.length// ✅ 现在安全了}getLength('hello')// ✅ 字符串有 lengthgetLength([1,2,3])// ✅ 数组有 lengthgetLength('abc')// ✅// getLength(123) // ❌ 报错:数字没有 length 属性三、开发中的常见错误与避坑指南
| 常见错误 | 原因 | 解决方法 |
|---|---|---|
| 滥用类型断言掩盖真实类型错误 | 断言只在编译时生效,运行时无法保证类型正确 | 先用typeof或instanceof做判断,断言只在你 100% 确定时使用 |
| 泛型函数内部访问属性报错 | 泛型太灵活,TS 无法确认类型是否有该属性 | 使用泛型约束extends限制类型 |
四、本篇小结
| 知识点 | 示例 | 说明 |
|---|---|---|
| 类型断言 as | value as string | 告诉 TS 值的类型 |
| as const | 'red' as const | 推断为最精确的字面量类型 |
| 泛型接口 | interface Res<T> { data: T } | 接口中使用泛型 |
| 泛型别名 | type Res<T> = { data: T } | 类型别名中使用泛型 |
| 泛型函数 | function fn<T>(v: T): T | 函数中使用泛型 |
| 泛型约束 | T extends Length | 限制 T 必须满足某个条件 |
核心记忆口诀
断言用 as,泛型占位 T 少不了。约束用 extends 定边界,复用灵活又可靠。
参考资料
- TypeScript 官方文档 —— 类型断言
- TypeScript 官方文档 —— 泛型
- 上一篇:TypeScript 核心语法进阶 —— 字面量类型与类型推论
- 下一篇:TypeScript 核心语法应用 —— Vue 3 中的使用(上)