← 返回首页

TypeScript 高级类型技巧

发布于 2025-01-04 | 作者:水码 | 阅读约 10 分钟

TypeScript 的类型系统远比想象中强大。除了基础的类型注解,它还提供了丰富的高级类型特性,能帮助我们编写更安全、更智能的代码。本文将介绍一些实用的高级类型技巧,助你从 TS 新手进阶为类型体操高手。

一、泛型:类型的函数

泛型让我们编写可复用的类型安全代码:

// 基础泛型函数
function identity<T>(arg: T): T {
  return arg
}

const num = identity(42)      // 类型推断为 number
const str = identity('hello') // 类型推断为 string

// 泛型约束
interface HasLength {
  length: number
}

function logLength<T extends HasLength>(arg: T): void {
  console.log(arg.length)
}

logLength('hello')    // ✅ 字符串有 length
logLength([1, 2, 3])  // ✅ 数组有 length
logLength(123)        // ❌ 数字没有 length

二、条件类型

条件类型让类型系统具备"逻辑判断"能力:

// 基本语法:T extends U ? X : Y
type IsString<T> = T extends string ? 'yes' : 'no'

type A = IsString<string>  // 'yes'
type B = IsString<number>  // 'no'

// 实用示例:提取函数返回类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never

function greet() {
  return { message: 'Hello', code: 200 }
}

type GreetReturn = ReturnType<typeof greet>
// { message: string; code: number }

三、infer 关键字

infer 用于在条件类型中提取类型:

// 提取数组元素类型
type ArrayElement<T> = T extends (infer E)[] ? E : never

type NumArr = ArrayElement<number[]>  // number
type StrArr = ArrayElement<string[]>  // string

// 提取 Promise 内部类型
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T

type P1 = UnwrapPromise<Promise<string>>  // string
type P2 = UnwrapPromise<number>           // number

// 提取函数参数类型
type Parameters<T> = T extends (...args: infer P) => any ? P : never

function add(a: number, b: string) { return a + b }
type AddParams = Parameters<typeof add>  // [number, string]

四、映射类型

映射类型可以基于已有类型创建新类型:

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

// 所有属性变为可选
type Partial<T> = {
  [K in keyof T]?: T[K]
}

type PartialUser = Partial<User>
// { id?: number; name?: string; email?: string }

// 所有属性变为只读
type Readonly<T> = {
  readonly [K in keyof T]: T[K]
}

// 挑选指定属性
type Pick<T, K extends keyof T> = {
  [P in K]: T[P]
}

type UserBasic = Pick<User, 'id' | 'name'>
// { id: number; name: string }

// 排除指定属性
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>

type UserWithoutEmail = Omit<User, 'email'>
// { id: number; name: string }

五、模板字面量类型

TypeScript 4.1 引入的强大特性:

// 基础用法
type Greeting = `Hello, ${string}!`
const g1: Greeting = 'Hello, World!'  // ✅
const g2: Greeting = 'Hi, World!'     // ❌

// 组合生成类型
type Direction = 'top' | 'right' | 'bottom' | 'left'
type Size = 'sm' | 'md' | 'lg'

type Margin = `margin-${Direction}-${Size}`
// 'margin-top-sm' | 'margin-top-md' | ... 共 12 种

// 实用:CSS-in-JS 属性名转换
type CamelToKebab<S extends string> = 
  S extends `${infer Head}${infer Tail}`
    ? Tail extends Uncapitalize<Tail>
      ? `${Lowercase<Head>}${CamelToKebab<Tail>}`
      : `${Lowercase<Head>}-${CamelToKebab<Tail>}`
    : S

type Test = CamelToKebab<'backgroundColor'>  // 'background-color'

六、类型守卫与收窄

// 使用 typeof
function process(value: string | number) {
  if (typeof value === 'string') {
    return value.toUpperCase()  // 这里 value 是 string
  }
  return value.toFixed(2)  // 这里 value 是 number
}

// 使用 in 操作符
interface Dog { bark(): void }
interface Cat { meow(): void }

function speak(animal: Dog | Cat) {
  if ('bark' in animal) {
    animal.bark()  // Dog
  } else {
    animal.meow()  // Cat
  }
}

// 自定义类型守卫
function isString(value: unknown): value is string {
  return typeof value === 'string'
}

function demo(value: unknown) {
  if (isString(value)) {
    console.log(value.length)  // 安全访问 string 方法
  }
}

七、实用工具类型

// Record:构建对象类型
type PageInfo = { title: string }
type Pages = Record<'home' | 'about' | 'contact', PageInfo>
// { home: PageInfo; about: PageInfo; contact: PageInfo }

// Extract & Exclude:联合类型操作
type T1 = Extract<'a' | 'b' | 'c', 'a' | 'f'>  // 'a'
type T2 = Exclude<'a' | 'b' | 'c', 'a'>        // 'b' | 'c'

// NonNullable:排除 null 和 undefined
type T3 = NonNullable<string | null | undefined>  // string

// Required:所有属性变为必选
interface Config {
  host?: string
  port?: number
}
type RequiredConfig = Required<Config>
// { host: string; port: number }

八、实战示例:API 响应类型

// 定义通用 API 响应结构
type ApiResponse<T> = 
  | { success: true; data: T; error: null }
  | { success: false; data: null; error: string }

// 使用
async function fetchUser(id: number): Promise<ApiResponse<User>> {
  try {
    const res = await fetch(`/api/users/${id}`)
    const data = await res.json()
    return { success: true, data, error: null }
  } catch (e) {
    return { success: false, data: null, error: (e as Error).message }
  }
}

// 调用时自动收窄
const result = await fetchUser(1)
if (result.success) {
  console.log(result.data.name)  // ✅ data 一定存在
} else {
  console.log(result.error)      // ✅ error 一定存在
}

九、学习资源

掌握这些高级类型技巧,你将能够编写更健壮的代码,在编译时就捕获潜在错误。类型系统不是束缚,而是最可靠的"文档"和"测试"。继续探索 TypeScript 的类型魔法吧!