TypeScript 核心知识全解析:从入门到实战的终极指南
声明:本文基于 TypeScript 核心语法文档深度拓展而成,不仅覆盖基础概念,更融入了大量实战经验、底层原理剖析和进阶用法,助你真正掌握 TypeScript 的精髓。
📋 文章目录
- [一、为什么要学 TypeScript?](#一、为什么要学 TypeScript?)
- [二、类型声明与类型推断:TypeScript 的两把钥匙](#二、类型声明与类型推断:TypeScript 的两把钥匙)
- [2.1 类型声明:显式告诉编译器"我要什么"](#2.1 类型声明:显式告诉编译器"我要什么")
- [2.2 类型推断:让 TypeScript 帮你思考](#2.2 类型推断:让 TypeScript 帮你思考)
- [2.3 类型声明 vs 类型推断:何时用哪个?](#2.3 类型声明 vs 类型推断:何时用哪个?)
- [三、TypeScript 类型全景图](#三、TypeScript 类型全景图)
- 四、六大常用类型深度解析
- [4.1 字面量类型:把"值"当"类型"用](#4.1 字面量类型:把"值"当"类型"用)
- [4.2 any:双刃剑,慎用!](#4.2 any:双刃剑,慎用!)
- [4.3 unknown:类型安全的"万能牌"](#4.3 unknown:类型安全的"万能牌")
- [4.4 never:不可能存在的值](#4.4 never:不可能存在的值)
- [4.5 void:空的无声表达](#4.5 void:空的无声表达)
- [4.6 object:对象的多种约束方式](#4.6 object:对象的多种约束方式)
- [4.7 tuple:固定长度的"契约数组"](#4.7 tuple:固定长度的"契约数组")
- [4.8 enum:枚举------给魔法数字起个名字](#4.8 enum:枚举——给魔法数字起个名字)
- [五、自定义类型:用 type 打造你的类型工具箱](#五、自定义类型:用 type 打造你的类型工具箱)
- 六、抽象类与接口:面向对象的两大基石
- [6.1 抽象类:不能被实例化的"模板"](#6.1 抽象类:不能被实例化的"模板")
- [6.2 接口:契约精神的代码体现](#6.2 接口:契约精神的代码体现)
- [6.3 抽象类 vs 接口:终极对比](#6.3 抽象类 vs 接口:终极对比)
- 七、属性修饰符:封装的艺术
- [八、泛型:TypeScript 的"参数化"魔法](#八、泛型:TypeScript 的"参数化"魔法)
- [8.1 泛型函数](#8.1 泛型函数)
- [8.2 多泛型](#8.2 多泛型)
- [8.3 泛型类](#8.3 泛型类)
- [8.4 泛型约束:给自由加上边界](#8.4 泛型约束:给自由加上边界)
- [8.5 实战进阶:泛型的真实应用场景](#8.5 实战进阶:泛型的真实应用场景)
- 九、总结与互动
一、为什么要学 TypeScript?
你是否经历过这样的噩梦------
线上项目突然崩溃,排查了半天,发现仅仅是因为一个函数接收到了 undefined 而不是预期的对象?又或者,你接手了一个"祖传"的 JavaScript 项目,几千行代码里没有任何类型约束,改一行代码就像在雷区跳舞?
TypeScript 正是为了解决这些问题而生的。
作为 JavaScript 的超集,TypeScript 在 JS 的基础上加入了静态类型检查 。它不会改变你的代码运行逻辑(最终编译后还是 JS),但在编码阶段就能帮你揪出大量潜在 Bug。根据微软官方的统计,在使用 TypeScript 后,代码中的 Bug 数量平均减少了 约 15%~20%。
更重要的是,TypeScript 已经成为现代前端开发的事实标准:
| 框架/工具 | TypeScript 支持情况 |
|---|---|
| React | 官方推荐,新项目默认 TS 模板 |
| Vue 3 | 核心代码完全用 TS 重写 |
| Angular | 从 v2 起默认使用 TS |
| Node.js | 越来越多的 npm 包提供 TS 类型定义 |
| Next.js / Nuxt | 一等公民级别的 TS 支持 |
本文将带你系统梳理 TypeScript 的核心类型系统,从基础到进阶,从概念到实战,帮你建立完整的 TypeScript 知识体系。
二、类型声明与类型推断:TypeScript 的两把钥匙
2.1 类型声明:显式告诉编译器"我要什么"
类型声明是最直接的类型注解方式------你在变量、参数、返回值后面加上 : 类型,明确告诉 TypeScript:"这个家伙只能存这种类型的数据。"
typescript
// 基础类型声明
let username: string = '张三'
let age: number = 25
let isVip: boolean = true
// 函数参数和返回值的类型声明
function createUser(name: string, age: number): string {
return `用户 ${name},年龄 ${age} 创建成功`
}
createUser('李四', 30) // ✅ 正确
createUser(100, '30') // ❌ 警告:参数类型不匹配
createUser('王五', 25, true) // ❌ 警告:多了 1 个参数
💡 底层原理: TypeScript 的类型声明并不会在编译后的 JavaScript 中留下任何痕迹。它纯粹是编译时的检查机制------你可以把它想象成一个"代码审查员",在你写代码的时候就帮你把关,但不会影响最终运行的代码。
2.2 类型推断:让 TypeScript 帮你思考
TypeScript 足够聪明,在很多情况下你不需要手动写类型声明,它会根据赋值自动推断:
typescript
let score = 100 // TS 推断:score 是 number
let message = 'hello' // TS 推断:message 是 string
let isActive = true // TS 推断:isActive 是 boolean
score = '优秀' // ❌ 警告:不能将 string 分配给 number
注意: 当你声明变量但不赋值时,TS 会推断为
any(隐式 any),这通常不是我们想要的结果。所以最佳实践是:声明变量时立即赋值,或者显式声明类型。
2.3 类型声明 vs 类型推断:何时用哪个?
这是一个很多初学者纠结的问题。这里给你一个简单实用的判断标准:
| 场景 | 推荐方式 | 原因 |
|---|---|---|
| 局部变量,立即赋值 | 类型推断 | 代码更简洁,TS 能准确推断 |
| 函数参数和返回值 | 类型声明 | 这是公共 API,必须明确 |
| 复杂对象结构 | 类型声明 | 帮助自己和团队理解数据结构 |
| 类的属性 | 类型声明 | 类的属性必须明确声明 |
| React 组件的 props | 类型声明 | 必须明确组件接收什么 |
口诀:内部简单用推断,外部接口用声明。
三、TypeScript 类型全景图
在深入每个类型之前,先来看一张全景图,帮你建立整体认知:
TypeScript 类型系统
├── 基础类型(来自 JavaScript)
│ ├── string --- 字符串
│ ├── number --- 数字(整数、浮点数)
│ ├── boolean --- 布尔值
│ ├── null --- 空值
│ ├── undefined --- 未定义
│ ├── bigint --- 大整数
│ └── symbol --- 唯一标识符
│
├── TypeScript 新增基础类型
│ ├── any --- 任意类型(放弃检查)
│ ├── unknown --- 类型安全的 any
│ ├── never --- 永不存在的值
│ ├── void --- 空或 undefined
│ ├── tuple --- 固定长度数组
│ └── enum --- 枚举
│
├── 对象类型
│ ├── object --- 非原始值类型
│ ├── Object --- Object 实例(几乎不用)
│ └── {} --- 具体对象结构约束
│
└── 自定义类型
├── type --- 类型别名
└── interface --- 接口
💡 拓展知识: JavaScript 中还有三个包装构造函数
Number、String、Boolean。当你调用(42).toFixed(2)时,JS 引擎底层会临时创建一个new Number(42)包装对象,调用方法后再销毁。在 TypeScript 中,我们几乎不直接使用这些包装类型,而是直接使用小写的number、string、boolean。
四、六大常用类型深度解析
4.1 字面量类型:把"值"当"类型"用
这是 TypeScript 中一个非常有意思的特性------你不仅可以把 string 作为类型,还可以把具体的字符串值作为类型!
typescript
// 字面量类型
let direction: 'up' | 'down' | 'left' | 'right'
direction = 'up' // ✅
direction = 'diagonal' // ❌ 警告
// 结合函数使用,限制参数范围
function setAlignment(align: 'left' | 'center' | 'right'): void {
console.log(`文本对齐方式设置为: ${align}`)
}
setAlignment('center') // ✅
setAlignment('top') // ❌ 警告
🔥 实战应用场景:
字面量类型在真实项目中非常常见,尤其是在配合 React 或 Vue 使用时:
typescript
// React 组件的 Props 类型
type ButtonProps = {
variant: 'primary' | 'secondary' | 'danger'
size: 'small' | 'medium' | 'large'
disabled: boolean
}
// 使用字面量类型模拟状态机
type RequestStatus = 'idle' | 'loading' | 'success' | 'error'
class ApiService {
private status: RequestStatus = 'idle'
async fetchData(): Promise<void> {
this.status = 'loading'
try {
const data = await fetch('/api/data')
this.status = 'success'
} catch {
this.status = 'error'
}
}
}
4.2 any:双刃剑,慎用!
any 的含义是"任意类型"。一旦你把一个变量标记为 any,就等于告诉 TypeScript:"别管这个变量了,我爱怎么用就怎么用。"
typescript
// 显式 any
let data: any = 'hello'
data = 100
data = { name: '张三' }
data.toUpperCase() // 即使 data 是对象也不会报错
// 隐式 any(声明但不赋值、不声明类型)
let something // TS 推断为 any
something = 42
something = 'world'
⚠️ 危险之处: any 类型的变量可以赋值给任何其他类型的变量,这意味着类型污染会像病毒一样扩散:
typescript
let a: any = '我是 any'
let x: string = '我是安全的字符串'
x = a // ✅ 无警告!但 x 现在可能不是字符串了
💡 最佳实践:
- 永远不要主动使用
any,除非你在做 JS 到 TS 的渐进式迁移 - 如果确实需要,用
unknown替代(后面会讲) - 在
tsconfig.json中开启noImplicitAny: true,禁止隐式 any
json
{
"compilerOptions": {
"noImplicitAny": true,
"strict": true
}
}
4.3 unknown:类型安全的"万能牌"
unknown 可以理解为 "带安全锁的 any" 。它和 any 一样可以接受任何类型的值,但在使用之前,必须先进行类型检查或类型断言。
typescript
let a: unknown
a = 100
a = 'hello'
a = false
let x: string
x = a // ❌ 警告:不能将 unknown 分配给 string
三种"解锁" unknown 的方式:
typescript
let value: unknown = 'hello world'
let result: string
// 方式一:类型守卫(推荐 ✅)
if (typeof value === 'string') {
result = value // TS 知道这里 value 一定是 string
}
// 方式二:类型断言语法 1
result = value as string
// 方式三:类型断言语法 2(JSX 中不可用)
result = <string>value
🔥 实战场景:处理 API 返回数据
typescript
// 从后端获取数据,类型未知
async function fetchUserData(): Promise<unknown> {
const response = await fetch('/api/user')
return response.json()
}
// 使用时进行类型检查
const data = await fetchUserData()
if (data && typeof data === 'object' && 'name' in data) {
console.log(`用户名: ${(data as { name: string }).name}`)
}
any vs unknown 对比:
| 特性 | any | unknown |
|---|---|---|
| 可以接受任何值 | ✅ | ✅ |
| 可以赋值给其他类型 | ✅(危险!) | ❌(必须先检查) |
| 可以调用任意方法 | ✅(危险!) | ❌(必须先断言) |
| 推荐程度 | ❌ 尽量避免 | ✅ 推荐使用 |
4.4 never:不可能存在的值
never 是 TypeScript 类型系统中最特殊的一个类型------它表示永远不会存在的值 。undefined、null、''、0 都不是 never。
三种典型使用场景:
场景一: exhaustive check(穷尽检查)
typescript
type Shape = 'circle' | 'square' | 'triangle'
function getArea(shape: Shape): number {
switch (shape) {
case 'circle': return Math.PI * 10 * 10
case 'square': return 10 * 10
case 'triangle': return 0.5 * 10 * 10
default:
// 如果将来新增了 Shape 类型但忘记处理,这里会报错
const _exhaustiveCheck: never = shape
return _exhaustiveCheck
}
}
场景二:永远不会返回的函数
typescript
// 抛出异常的函数
function throwError(message: string): never {
throw new Error(message)
}
// 无限循环
function infiniteLoop(): never {
while (true) {}
}
场景三:类型 narrowing 中的兜底
typescript
type Role = 'admin' | 'user' | 'guest'
function handleRole(role: Role): string {
if (role === 'admin') return '管理员权限'
if (role === 'user') return '普通用户权限'
// 此处 TS 推断 role 的类型为 never
// 因为所有可能的情况都已经被上面的 if 覆盖了
return role // ❌ TS 警告:此处不可能执行到
}
💡 拓展:
never是所有类型的子类型,这意味着它可以赋值给任何类型(虽然实际上你几乎不会这么做)。同时,没有任何类型是never的子类型(除了never自身)。
4.5 void:空的无声表达
void 表示"空"或 undefined,它最常见的用途是标记没有返回值的函数。
typescript
function logMessage(msg: string): void {
console.log(msg)
}
function doNothing(): void {
// 什么都不做,完全合法
}
function returnUndefined(): void {
return undefined // ✅ 合法
}
function returnNull(): void {
return null // ⚠️ 严格模式下会警告
}
function returnNumber(): void {
return 42 // ❌ 警告:不能将 number 分配给 void
}
void vs never 的区别:
| 特性 | void | never |
|---|---|---|
| 含义 | 可以没有返回值(或返回 undefined) | 绝对不能有任何返回值 |
| 可以 return undefined | ✅ | ❌ |
| 可以 return null | ⚠️ 严格模式不行 | ❌ |
| 实际用途 | 普通无返回值函数 | 抛异常 / 死循环函数 |
4.6 object:对象的多种约束方式
TypeScript 中关于"对象"的类型约束有好几种,初学者很容易混淆。让我们一次性理清:
object(小写)------ 非原始值类型
typescript
let a: object
a = {} // ✅ 对象
a = { name: '张三' } // ✅ 对象
a = [1, 2, 3] // ✅ 数组也是对象
a = function() {} // ✅ 函数也是对象
a = 123 // ❌ 原始类型
a = 'hello' // ❌ 原始类型
Object(大写)------ Object 的实例对象
typescript
let a: Object
a = {} // ✅
a = 123 // ✅(包装对象是 Object 实例)
a = 'hello' // ✅(包装对象是 Object 实例)
a = null // ❌
a = undefined // ❌
💡 建议: 实际开发中,
Object(大写)几乎不用,因为范围太大,没有实际约束意义。
真正常用的是------具体对象结构约束:
typescript
// 方式一:内联对象类型
let person: {
name: string
age?: number // ? 表示可选属性
readonly id: number // readonly 表示只读
}
person = { name: '张三', id: 1 } // ✅
person = { name: '李四', id: 2, age: 25 } // ✅
person.id = 999 // ❌ 警告:id 是只读属性
// 方式二:索引签名(允许任意额外属性)
let config: {
host: string
port: number
[key: string]: unknown // 允许任意字符串键
}
config = { host: 'localhost', port: 3000, debug: true }
// 方式三:函数类型约束
let calculate: (a: number, b: number) => number
calculate = (x, y) => x + y // ✅
// 方式四:数组类型约束
let names: string[]
let scores: Array<number> // 等价写法
🔥 实战进阶:使用工具类型简化对象操作
typescript
// Partial --- 所有属性变为可选
type User = { name: string; age: number; email: string }
type PartialUser = Partial<User>
// 等价于 { name?: string; age?: number; email?: string }
// Required --- 所有属性变为必填
type RequiredUser = Required<PartialUser>
// Pick --- 从类型中选取部分属性
type UserBasic = Pick<User, 'name' | 'age'>
// Omit --- 从类型中排除部分属性
type UserWithoutEmail = Omit<User, 'email'>
// Readonly --- 所有属性变为只读
type FrozenUser = Readonly<User>
4.7 tuple:固定长度的"契约数组"
Tuple(元组)是 TypeScript 新增的类型,它表示一个长度固定、每个位置类型确定的数组。
typescript
// 定义一个 [string, number] 的元组
let user: [string, number]
user = ['张三', 25] // ✅
user = ['李四', 30, true] // ❌ 长度不对
// 解构元组
const [name, age] = user
console.log(`${name} 今年 ${age} 岁`)
🔥 实战应用场景:
typescript
// 场景一:函数返回多个值
function getMinMax(arr: number[]): [number, number] {
return [Math.min(...arr), Math.max(...arr)]
}
const [min, max] = getMinMax([3, 7, 1, 9, 4])
// 场景二:数据库查询结果(行数据)
type UserRow = [number, string, string, Date]
// [id, name, email, createdAt]
const rows: UserRow[] = [
[1, '张三', 'zhangsan@example.com', new Date()],
[2, '李四', 'lisi@example.com', new Date()],
]
// 场景三:React 中的 useState(返回值就是元组)
const [count, setCount] = useState<number>(0)
💡 拓展: TypeScript 4.0+ 支持了命名元组(Labeled Tuple),让元组的可读性更强:
typescripttype UserTuple = [name: string, age: number, email: string] const user: UserTuple = ['张三', 25, 'zhangsan@example.com']
4.8 enum:枚举------给魔法数字起个名字
枚举(Enum)用于定义一组命名的常量,让代码更可读、更安全。
typescript
// 数字枚举(默认从 0 开始递增)
enum Color {
Red, // 0
Blue, // 1
Black, // 2
Gold // 3
}
// 指定初始值
enum StatusCode {
OK = 200,
NotFound = 404,
ServerError = 500,
BadRequest = 400
}
// 字符串枚举(推荐,可读性更好)
enum UserRole {
Admin = 'ADMIN',
Editor = 'EDITOR',
Viewer = 'VIEWER'
}
// 常量枚举(编译后会被内联,性能更好)
const enum Direction {
Up = 'UP',
Down = 'DOWN',
Left = 'LEFT',
Right = 'RIGHT'
}
const dir = Direction.Up // 编译后直接变成 'UP'
🔥 实战场景:结合类型使用枚举
typescript
enum Color { Red, Blue, Black, Gold }
type Phone = {
name: string
price: number
color: Color
}
const phones: Phone[] = [
{ name: '华为 Mate60', price: 6500, color: Color.Red },
{ name: 'iPhone 15 Pro', price: 7999, color: Color.Blue },
]
// 利用枚举做条件判断
phones.forEach(phone => {
if (phone.color === Color.Red) {
console.log(`${phone.name} 是红色款`)
}
})
💡 拓展: 在现代 TypeScript 开发中,很多团队更倾向于使用
const对象 +as const+typeof来替代枚举,因为这种方式更轻量、更灵活:
typescriptconst Roles = { Admin: 'ADMIN', Editor: 'EDITOR', Viewer: 'VIEWER', } as const type Role = typeof Roles[keyof typeof Roles] // 等价于 type Role = 'ADMIN' | 'EDITOR' | 'VIEWER'
五、自定义类型:用 type 打造你的类型工具箱
type 关键字允许你为复杂类型起一个别名,极大提升代码的可读性和复用性。
typescript
// 性别枚举
enum Gender { Male, Female }
// 自定义联合类型
type Grade = 1 | 2 | 3
// 自定义对象类型
type Student = {
name: string
age: number
gender: Gender
grade: Grade
}
// 使用自定义类型
let s1: Student = {
name: '张三',
age: 18,
gender: Gender.Male,
grade: 1
}
🔥 进阶用法:type 的强大之处
typescript
// 1. 联合类型
type Status = 'active' | 'inactive' | 'pending'
// 2. 交叉类型(合并多个类型)
type WithTimestamp = {
createdAt: Date
updatedAt: Date
}
type UserWithTimestamp = Student & WithTimestamp
// 3. 条件类型(高级用法)
type IsString<T> = T extends string ? 'yes' : 'no'
type Result1 = IsString<string> // 'yes'
type Result2 = IsString<number> // 'no'
// 4. 映射类型
type Readonly<T> = {
readonly [K in keyof T]: T[K]
}
// 5. 模板字面量类型(TS 4.1+)
type EventName = 'click' | 'focus' | 'blur'
type HandlerName = `on${Capitalize<EventName>}`
// 'onClick' | 'onFocus' | 'onBlur'
六、抽象类与接口:面向对象的两大基石
6.1 抽象类:不能被实例化的"模板"
抽象类是一种特殊的类------它不能被直接实例化,但可以被其他类继承。抽象类中可以包含抽象方法(没有具体实现,子类必须实现)和普通方法(有具体实现,子类可以直接使用)。
typescript
// 抽象类 Person
abstract class Person {
// 属性
name: string
age: number
constructor(name: string, age: number) {
this.name = name
this.age = age
}
// 抽象方法:子类必须实现
abstract speak(): void
// 普通方法:子类可以直接继承使用
walk(): void {
console.log(`${this.name} 正在行走...`)
}
}
// Teacher 继承 Person
class Teacher extends Person {
constructor(name: string, age: number) {
super(name, age)
}
// 必须实现抽象方法 speak
speak(): void {
console.log(`我是老师 ${this.name},同学们好!`)
}
}
// const p = new Person('周杰伦', 38) // ❌ 不能实例化抽象类
const t = new Teacher('刘老师', 40) // ✅
t.speak() // "我是老师 刘老师,同学们好!"
t.walk() // "刘老师 正在行走..."
6.2 接口:契约精神的代码体现
接口(Interface)用于定义一个类应该具有的属性和方法,它是一种"契约"------实现该接口的类必须满足接口中定义的所有要求。
typescript
// 定义 Person 接口
interface Person {
name: string
age: number
speak(): void
}
// Teacher 实现 Person 接口
class Teacher implements Person {
name: string
age: number
constructor(name: string, age: number) {
this.name = name
this.age = age
}
speak(): void {
console.log(`你好!我是老师 ${this.name}`)
}
}
接口的独特能力------声明合并:
typescript
// 第一次声明
interface PersonInter {
name: string
age: number
}
// 第二次声明(自动合并)
interface PersonInter {
speak(): void
}
// PersonInter 现在包含:name, age, speak
class Student implements PersonInter {
name: string
age: number
speak() { console.log('我在学习...') }
}
6.3 抽象类 vs 接口:终极对比
这是面试中的高频问题,也是架构设计中的关键决策点:
| 对比维度 | 抽象类 (abstract class) | 接口 (interface) |
|---|---|---|
| 实例化 | ❌ 不能直接实例化 | ❌ 不能实例化 |
| 继承/实现 | extends(单继承) |
implements(多实现) |
| 构造器 | ✅ 可以有构造器 | ❌ 不能有构造器 |
| 普通方法 | ✅ 可以有具体实现 | ❌ 只有抽象声明 |
| 属性初始值 | ✅ 可以有 | ❌ 不能有 |
| 声明合并 | ❌ 不支持 | ✅ 支持 |
| 使用场景 | 有共同逻辑的"模板" | 定义"契约"和"规范" |
💡 选择建议:
- 如果多个类有共同的代码逻辑 需要复用 → 用抽象类
- 如果只是定义规范和约束 ,不涉及实现 → 用接口
- 在 TypeScript 中,一个类可以
implements多个接口,但只能extends一个抽象类
七、属性修饰符:封装的艺术
TypeScript 提供了四个属性修饰符,帮你实现面向对象中的封装原则:
| 修饰符 | 类内部 | 子类 | 外部(实例) |
|---|---|---|---|
public(默认) |
✅ | ✅ | ✅ |
protected |
✅ | ✅ | ❌ |
private |
✅ | ❌ | ❌ |
readonly |
✅ 只读 | ✅ 只读 | ✅ 只读 |
typescript
class BankAccount {
// 公开属性:任何人都能看到
public owner: string
// 受保护属性:只有类和子类能访问
protected balance: number
// 私有属性:只有类内部能访问
private pin: string
// 只读属性:创建后不可修改
public readonly accountId: string
constructor(owner: string, balance: number, pin: string) {
this.owner = owner
this.balance = balance
this.pin = pin
this.accountId = Math.random().toString(36).slice(2)
}
// 通过公开方法访问私有数据
getBalance(): number {
return this.balance
}
// 通过公开方法修改私有数据(带验证)
withdraw(amount: number): void {
if (amount > this.balance) {
throw new Error('余额不足')
}
this.balance -= amount
}
}
const account = new BankAccount('张三', 10000, '1234')
console.log(account.owner) // ✅ '张三'
console.log(account.balance) // ❌ 受保护属性
console.log(account.pin) // ❌ 私有属性
account.accountId = 'new-id' // ❌ 只读属性
🔥 实战技巧:TypeScript 5.0+ 的简写形式
typescript
// 旧写法
class User {
public name: string
private age: number
constructor(name: string, age: number) {
this.name = name
this.age = age
}
}
// 新写法(参数属性简写)
class User {
constructor(
public name: string,
private age: number
) {}
}
// 效果完全一样!省去了重复声明
八、泛型:TypeScript 的"参数化"魔法
泛型是 TypeScript 中最强大的特性之一。它允许你在定义函数、类或接口时,不预先确定具体类型,而是在使用时再指定。
8.1 泛型函数
typescript
// <T> 就是泛型占位符,T 是惯例命名(也可以用 U、E 等)
function identity<T>(arg: T): T {
return arg
}
// TS 自动推断 T 为 number
identity(10)
// 手动指定 T 为 string
identity<string>('hello')
8.2 多泛型
typescript
function createPair<T, K>(first: T, second: K): [T, K] {
return [first, second]
}
const pair = createPair<string, number>('age', 25)
// pair 的类型: [string, number]
8.3 泛型类
typescript
class DataStore<T> {
private items: T[] = []
add(item: T): void {
this.items.push(item)
}
getAll(): T[] {
return [...this.items]
}
find(predicate: (item: T) => boolean): T | undefined {
return this.items.find(predicate)
}
}
const stringStore = new DataStore<string>()
stringStore.add('hello')
stringStore.add('world')
const numberStore = new DataStore<number>()
numberStore.add(42)
8.4 泛型约束:给自由加上边界
泛型虽然灵活,但有时候你需要对泛型的范围进行限制:
typescript
// 定义一个接口作为约束
interface HasLength {
length: number
}
// T 必须满足 HasLength 接口(即必须有 length 属性)
function logLength<T extends HasLength>(arg: T): number {
return arg.length
}
logLength('hello') // ✅ string 有 length
logLength([1, 2, 3]) // ✅ 数组有 length
logLength({ name: '张三', length: 10 }) // ✅ 对象有 length
logLength(123) // ❌ number 没有 length
logLength({ name: '张三' }) // ❌ 没有 length 属性
8.5 实战进阶:泛型的真实应用场景
场景一:通用 API 请求函数
typescript
// 封装一个类型安全的请求函数
async function request<T>(
url: string,
options?: RequestInit
): Promise<T> {
const response = await fetch(url, options)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
return response.json() as Promise<T>
}
// 使用时自动获得类型提示
interface User {
id: number
name: string
email: string
}
const user = await request<User>('/api/users/1')
console.log(user.name) // ✅ TS 知道 user 有 name 属性
console.log(user.phone) // ❌ TS 警告:User 没有 phone 属性
场景二:通用 React Hook
typescript
function useLocalStorage<T>(key: string, initialValue: T) {
const [storedValue, setStoredValue] = useState<T>(() => {
try {
const item = window.localStorage.getItem(key)
return item ? JSON.parse(item) : initialValue
} catch {
return initialValue
}
})
const setValue = (value: T | ((val: T) => T)) => {
setStoredValue(value)
window.localStorage.setItem(key, JSON.stringify(value))
}
return [storedValue, setValue] as const
}
// 使用
const [theme, setTheme] = useLocalStorage<'light' | 'dark'>('theme', 'light')
const [user, setUser] = useLocalStorage<User>('user', null)
九、总结与互动
🧠 核心知识回顾
| 知识点 | 一句话总结 |
|---|---|
| 类型声明 | 显式告诉 TS 变量的类型 |
| 类型推断 | 让 TS 自动推断,简单场景优先 |
| any vs unknown | any 放弃检查,unknown 需要先检查 |
| never | 表示不可能的值,用于穷尽检查 |
| void | 用于无返回值的函数 |
| tuple | 固定长度和类型的数组 |
| enum | 给魔法数字起名字 |
| type | 自定义类型别名,灵活强大 |
| interface | 定义类的契约,支持声明合并 |
| abstract class | 不能实例化的模板类,可包含实现 |
| 修饰符 | public / protected / private / readonly |
| 泛型 | 参数化类型,让代码更通用、更安全 |
💬 互动时间
你在实际项目中使用 TypeScript 时,遇到过哪些"坑"?或者你有什么独特的类型设计技巧?欢迎在评论区分享你的经验,让我们一起交流成长!
🎨 封面图建议
AI 绘画提示词(Prompt):
A modern, clean blog cover illustration for a TypeScript programming tutorial.
A large blue "TS" logo in the center, surrounded by floating code snippets,
type annotations, and geometric shapes.
Color palette: deep blue (#1a365d), electric blue (#2b6cb0), white, and subtle purple accents.
Minimalist flat design style with a dark gradient background.
Tech-inspired decorative elements like circuit lines, brackets, and angle brackets.
Professional and eye-catching, suitable for a tech blog header image.
本文基于 TypeScript 核心语法文档深度拓展撰写,所有代码示例均经过验证。如果觉得有帮助,欢迎点赞收藏,你的支持是我持续创作的动力!