2026.01.01

TS 泛型与断言

TypeScript 泛型定义、约束与类型断言

为什么要用泛型?

假设有一个简单的需求:封装一个函数,传入任意类型的参数,返回该参数本身。

对于 JavaScript 来说,这非常简单:

const fn = value => value

但在 TypeScript 中,我们可能会想到上一章学过的函数重载

function fn(value: number): number
function fn(value: string): string
function fn(value: boolean): boolean
function fn(value: unknown): unknown
function fn(value: unknown): unknown {
  return value
}

这种写法虽然可行,但需要为每种类型都写一遍重载。如果类型更多,代码会变得非常冗长。


泛型的基本用法

泛型允许在定义函数、接口或类时不指定具体类型,而是在使用时再指定。泛型利用了 TypeScript 的类型自动推导能力,可以让我们写出更灵活的代码:

function identity<T>(v: T): T {
  return v
}

function fn<T>(value: T): T {
  return value
}

const num = fn(42) // number
const str = fn('hello') // string
const bool = fn(true) // boolean

在函数名后使用 <> 来定义泛型 TT 就像一个占位符,表示传递的类型。

在接口中使用泛型

在实际开发中,后端返回的数据通常具有 code、data、message 这样的结构。其中 codemessage 是固定的,但 data 的内容是不确定的,这时就需要用到泛型:

interface ResultData<T> {
  message: string
  code: number
  data: T
}

interface User {
  id: number
  name: string
  age: number
}

type ResponseUserData = ResultData<User>
// {
//   message: string
//   code: number
//   data: User
// }

💡 tips:泛型通常使用大写字母表示,常见命名有 T(Type)、K(Key)、V(Value)等。


多泛型

泛型可以定义多个,用逗号分隔:

function swap<T, U>(tuple: [T, U]): [U, T] {
  return [tuple[1], tuple[0]]
}

const result = swap([1, 'hello'])
// [string, number]

function merge<T, U>(obj1: T, obj2: U): T & U {
  return { ...obj1, ...obj2 }
}

const merged = merge({ name: 'Alice' }, { age: 18 })
// { name: string } & { age: number }

泛型的默认类型

泛型可以指定默认类型,类似于函数的默认参数:

interface ResultData<T = any> {
  message: string
  code: number
  data: T
}

interface User {
  id: number
  name: string
  age: number
}

// 使用默认类型
type ResponseData = ResultData // data 的类型是 any

// 指定具体类型
type ResponseUserData = ResultData<User> // data 的类型是 User

💡 tips:当泛型参数较多,且大部分场景使用相同类型时,设置默认类型可以简化代码。


泛型约束

泛型虽然灵活,但有时过于宽泛,需要添加约束来限定泛型的范围。

使用 extends 约束

function getLength<T extends { length: number }>(value: T): number {
  return value.length
}

getLength('hello') // ✅ string 有 length 属性
getLength([1, 2, 3]) // ✅ array 有 length 属性
getLength({ length: 10 }) // ✅ 对象有 length 属性
getLength(42) // ❌ number 没有 length 属性

结合 keyof 的约束

function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key]
}

const user = {
  name: 'Alice',
  age: 18,
  email: 'alice@example.com'
}

const name = getProperty(user, 'name') // string
const age = getProperty(user, 'age') // number
const email = getProperty(user, 'email') // string

getProperty(user, 'gender')
// ❌ Argument of type '"gender"' is not assignable to parameter of type '"name" | "age" | "email"'.

⚠️ 泛型约束能让代码更安全,但也要注意不要过度约束,影响泛型的灵活性。


类型断言 as

虽然 TypeScript 会进行类型推导,但有时我们需要告诉编译器「我知道这是什么类型」,这时可以使用类型断言:

type MyType = string | number | boolean

function getLength(value: MyType) {
  console.log((value as string).length)
}

// 在类型非常确定的情况下使用
const inputDom = document.querySelector('input')
inputDom!.addEventListener('change', () => {
  console.log((inputDom as HTMLInputElement).value)
})

⚠️ 类型断言有明显的类型安全隐患,使用时必须明确知道值的实际类型,否则可能导致运行时错误。


非空断言 !

当确定值不是 nullundefined 时,可以使用 ! 进行非空断言:

function getRandom(length?: number) {
  if (!length) {
    return undefined
  }

  return Math.random().toString(36).slice(-length)
}

const s = getRandom(6)

// 如果没有 !,TypeScript 会报错:'s' is possibly 'undefined'
s!.length

⚠️ 使用非空断言时需要谨慎,确保值确实不为 nullundefined


satisfies 操作符

satisfies 是 TypeScript 4.9 引入的类型操作符,它与 as 断言类似,但更安全且智能

为什么使用 satisfies?

as 断言相比,satisfies 有两个优势:

  1. 在满足类型安全的前提下进行验证
  2. 保留字面量类型,提供更精确的类型提示

案例对比

interface User {
  name: string
  age: number
  [key: string]: any
}

// 使用类型注解:类型被拓宽,丢失额外属性的类型信息
const u1: User = {
  name: '张三',
  age: 18,
  id: 123
}
u1.id // ❌ 无提示,id 的类型是 any

// 使用 satisfies:保留精确类型
const u2 = {
  name: '张三',
  age: 18,
  id: 123
} satisfies User
u2.id // ✅ 有提示,id 的类型是 number

安全性验证

interface IConfig {
  test: string | number
}

// 使用 as:不安全的断言
const t1 = {} as IConfig
t1.test // ❌ t 中实际没有 test,但不会报错

// 使用 satisfies:编译时验证
const t2 = {} satisfies IConfig
// ❌ Type '{}' does not satisfy the expected type 'IConfig'.
// Property 'test' is missing in type '{}' but required in type 'IConfig'.

const t3 = {
  test: 123
} satisfies IConfig // ✅ 同时能根据 test 的值进行类型收窄

💡 tips:优先使用 satisfies 替代 as 断言,既能保证类型安全,又能获得精确的类型提示。