TypeScript核心知识全解析

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 中还有三个包装构造函数 NumberStringBoolean。当你调用 (42).toFixed(2) 时,JS 引擎底层会临时创建一个 new Number(42) 包装对象,调用方法后再销毁。在 TypeScript 中,我们几乎不直接使用这些包装类型,而是直接使用小写的 numberstringboolean


四、六大常用类型深度解析

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')    // ❌ 警告

🔥 实战应用场景:

字面量类型在真实项目中非常常见,尤其是在配合 ReactVue 使用时:

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 类型系统中最特殊的一个类型------它表示永远不会存在的值undefinednull''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),让元组的可读性更强:

typescript 复制代码
type 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 来替代枚举,因为这种方式更轻量、更灵活:

typescript 复制代码
const 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 核心语法文档深度拓展撰写,所有代码示例均经过验证。如果觉得有帮助,欢迎点赞收藏,你的支持是我持续创作的动力!

相关推荐
很楠爱上1 小时前
TypeScript 核心知识精要
javascript·ubuntu·typescript
云水一下11 小时前
TypeScript 从零基础到精通(五):高级类型与泛型
前端·javascript·typescript
云水一下11 小时前
TypeScript 从零基础到精通(六):类型声明与模块化
javascript·typescript
无聊的老谢12 小时前
Vue 3 + TypeScript 构建大型电信运维平台的前端架构设计
前端·vue.js·typescript
云水一下20 小时前
TypeScript 从零基础到精通(二):基础类型与类型系统
javascript·typescript
云水一下1 天前
TypeScript 从零基础到精通(四):面向对象编程(类与继承)
javascript·typescript
CDN3601 天前
【架构进阶】告别配置漂移!用 NodeNext + Workspace 打造优雅的 TypeScript Monorepo
前端·javascript·typescript
颂love1 天前
TypeScript速学
前端·javascript·typescript
wuhen_n1 天前
RAG 入门:检索增强生成核心原理
前端·人工智能·typescript·langchain·ai编程