TypeScript 高级特性 —— 类型断言与泛型

阅读本文你能学到什么?

  • 类型断言是什么,as 怎么用
  • 泛型是什么,为什么需要它
  • 泛型接口、泛型别名、泛型函数的基本用法
  • 泛型约束的作用与用法

前言

第四篇我们搞定了字面量类型和类型推论。但实际项目中,还有两个绕不开的问题:你比 TS 更清楚某个值的类型,怎么告诉它"相信我"?又或者,写一个函数,它能接收任意类型,又能保留传入的类型信息------怎么让类型跟着参数走?

第一个问题的答案是类型断言 ,第二个是泛型

这一篇,咱们就把这两个技能一起搞定。

一、类型断言

1.1 什么是类型断言?

类型断言是告诉 TypeScript 编译器"我知道这个值是什么类型,你按我说的来"。它不会在运行时改变值,只是让 TS 通过编译。

下面通过两个常见场景来理解它的用法。

示例一:DOM 元素获取

比如 document.getElementById('username'),TypeScript 只知道它返回的是 HTMLElement | null------它只知道页面上有个元素叫 username,但不知道它具体是什么标签,可能是 input,也可能是 div

但作为开发者,你清楚这个 username 对应的就是一个输入框:

typescript 复制代码
const usernameInput = document.getElementById('username') as HTMLInputElement
// 现在 TS 知道它是 HTMLInputElement,可以放心访问 .value
console.log(usernameInput.value)  // ✅

示例二:处理 unknown 类型

unknown 类型的值不能直接使用,需要用断言告诉 TS 它到底是什么类型。

typescript 复制代码
let value: unknown = 'Hello'
// value.length  // ❌ 报错:value 是 unknown

let len = (value as string).length  // ✅ 断言为 string 后可以访问

1.2 语法:推荐使用 as

TypeScript 有两种断言语法:<类型>值值 as 类型。在 .tsx 文件中,尖括号语法会与 JSX 标签产生解析冲突,所以推荐统一使用 as

typescript 复制代码
// ❌ 不推荐:尖括号语法(在 JSX 中会冲突)
let value = <string>'hello'

// ✅ 推荐:as 语法
let value = 'hello' as string

1.3 as const 断言

除了用 as 指定类型,TypeScript 还提供了 as const,用于控制类型推断的精度------它告诉 TypeScript 把类型推断为最精确的字面量类型

typescript 复制代码
// 没有 as const
let color = 'red'  // 推断为 string

// 有 as const
let color = 'red' as const  // 推断为 'red'

// 对象的 as const
const config = {
  url: '/api',
  method: 'GET'
} as const
// 推断为:{ readonly url: '/api'; readonly method: 'GET' }

as const 在需要精确字面量类型的场景非常有用,比如定义路由配置、常量对象等。

1.4 类型断言的注意事项

类型断言只能够"欺骗"TypeScript 编译器,无法避免运行时的错误。滥用类型断言可能会导致运行时错误。

typescript 复制代码
function processValue(value: string | number) {
  // 假设我们断言 value 是 number
  const num = value as number
  console.log(num.toFixed(2))
}

processValue('hello')  // ❌ 编译通过,但运行时报错:num.toFixed is not a function

💡 给新手的提醒: 类型断言应该在你比 TS 更清楚类型 的时候使用,而不是用来绕过类型检查。如果你不确定值的类型,建议先用 typeofinstanceof 在代码里做判断,而不是直接用断言。

二、泛型

2.1 什么是泛型?为什么需要它?

泛型(Generics)是指在定义接口、函数等类型的时候,不预先指定具体的类型,而在使用的时候再指定类型 的一种特性。使用泛型可以复用类型并且让类型更加灵活。

思考一下:下面的两种数据结构,如何使用 interface 接口实现类型注解?这样做有什么问题?

typescript 复制代码
// 用户数据
interface UserData {
  code: number
  message: string
  data: { id: number; name: string }
}

// 商品数据
interface ProductData {
  code: number
  message: string
  data: { id: number; name: string; price: number }
}

我们可以对比下数据结构,这两个接口的结构完全一样,只有 data 的类型不同。如果每种数据类型都写一个接口,代码会变得非常冗余------泛型就是来解决这个问题的

使用泛型后,只需要定义一次:

typescript 复制代码
interface ResponseData<T> {
  code: number
  message: string
  data: T
}

2.2 泛型接口

在接口interface后面使用 <T> 声明泛型参数,接口里的其他成员都可以使用该参数的类型。

语法: interface 接口名<T> { ... }

typescript 复制代码
// 定义泛型接口
interface ResponseData<T> {
  code: number
  message: string
  data: T
}

// 使用泛型接口
interface User {
  id: number
  name: string
}

interface Product {
  id: number
  name: string
  price: number
}

const userResponse: ResponseData<User> = {
  code: 200,
  message: '获取用户成功',
  data: { id: 1, name: '张三' }
}

const productResponse: ResponseData<Product> = {
  code: 200,
  message: '获取商品成功',
  data: { id: 1001, name: '手机', price: 2999 }
}

我们可以把泛型理解成一个函数------T 就像类型的"参数",可以传一个,也可以传多个,比如 ResponseData<T>ResponseData<T, U>

2.3 泛型别名

在类型别名 type 的后面使用 <T> 声明泛型参数,类型别名里的其他成员都可以使用该参数的类型。

语法: type 类型别名<T> = { ... }

typescript 复制代码
// 定义泛型别名
type ResponseData<T> = {
  code: number
  message: string
  data: T
}

type User = {
  id: number
  name: string
}

const response: ResponseData<User> = {
  code: 200,
  message: '获取用户成功',
  data: { id: 1, name: '张三' }
}

2.4 泛型函数

在函数名称的后面使用 <T> 声明泛型参数,整个函数中(参数、返回值、函数体)都可以使用该参数的类型。

语法: function 函数名<T>(参数: T): T { ... }

typescript 复制代码
// 需求:创建一个 createArray 函数
// 它可以创建一个指定长度的数组,同时将每一项都填充一个默认值(多种类型)

function createArray<T>(length: number, defaultValue: T): T[] {
  const arr = new Array(length)
  for (let i = 0; i < length; i++) {
    arr[i] = defaultValue
  }
  return arr
}

// 使用示例
const strArray = createArray<string>(3, 'hello')   // ['hello', 'hello', 'hello']
const numArray = createArray<number>(3, 100)       // [100, 100, 100]
const boolArray = createArray<boolean>(3, true)    // [true, true, true]

// 也可以让 TS 自动推断类型
const inferredArray = createArray(3, 'hello')      // TS 自动推断为 string[]

2.5 泛型约束

泛型的特点就是灵活不确定,有些时候泛型函数的内部需要访问一些特定类型的数据才有的属性,此时会有类型错误。

typescript 复制代码
function getLength<T>(value: T): number {
  // ❌ 报错:T 类型上不一定有 length 属性
  return value.length
}

解决方案:通过泛型约束来解决------使用 extends 关键字让传入的类型必须满足某个条件。

语法: T extends 类型 ------ 限制 T 必须是某个类型的子类型。

typescript 复制代码
// 定义一个有 length 属性的接口
interface Length {
  length: number
}

// 约束:T 必须具有 length 属性
function getLength<T extends Length>(value: T): number {
  return value.length  // ✅ 现在安全了
}

getLength('hello')      // ✅ 字符串有 length
getLength([1, 2, 3])    // ✅ 数组有 length
getLength('abc')        // ✅
// getLength(123)       // ❌ 报错:数字没有 length 属性

三、开发中的常见错误与避坑指南

常见错误 原因 解决方法
滥用类型断言掩盖真实类型错误 断言只在编译时生效,运行时无法保证类型正确 先用 typeofinstanceof 做判断,断言只在你 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 实战,看看在实际的 Vue 3 项目中,如何将 TS 语法应用到组件和逻辑中。

欢迎持续关注 🚀

参考资料

相关推荐
掘金酱15 小时前
「TRAE Work 实战帮」征文启动!你沉淀的经验,值得被看见!
前端·人工智能·后端
橙子家15 小时前
Windows 上同时安装多个 node 版本
前端
爱勇宝15 小时前
DeepSeek V4-Flash 更新:代码与 Agent 能力全面增强
前端·后端·deepseek
GuWenyue15 小时前
90%前端写React+TS都踩坑!从组件类型、单向数据流到本地存储完整实战
前端·react.js
妙码生花16 小时前
从 PHP 到 AI + Golang,程序员自救转型手记(四十五):前端远程下拉输入组件
前端·javascript·vue.js
明月_清风16 小时前
全栈工程师必会技术栈:从入门到架构的完整成长地图 🗺️
前端·后端·全栈
windliang16 小时前
Claude Code 源码分析(三):一次模型回答如何流进 Agent
前端·算法·ai编程
文心快码BaiduComate16 小时前
从空白页面到可运行网站,新手前端工具怎么选?
前端