TypeScript

TypeScript 高级类型技巧:提升代码质量

作者:君知博客团队
48 分钟阅读
#TypeScript#类型系统#泛型#类型安全#最佳实践

TypeScript 高级类型技巧:提升代码质量

TypeScript 的类型系统非常强大,掌握高级类型技巧可以让你的代码更加安全、可维护。本文将介绍一些实用的高级类型特性。

1. 泛型(Generics)

泛型是 TypeScript 中最强大的特性之一,它允许我们编写可重用的代码。

基础泛型

// 简单的泛型函数
function identity<T>(arg: T): T {
  return arg
}

// 使用
const result1 = identity<string>("hello") // 类型: string
const result2 = identity(42) // 类型推断: number

泛型约束

// 约束泛型必须有 length 属性
interface Lengthwise {
  length: number
}

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

logLength("hello") // ✅ 字符串有 length
logLength([1, 2, 3]) // ✅ 数组有 length
logLength({ length: 10, value: 3 }) // ✅ 对象有 length
// logLength(42) // ❌ 数字没有 length

泛型接口

// API 响应的通用接口
interface ApiResponse<T> {
  data: T
  status: number
  message: string
}

// 用户数据
interface User {
  id: number
  name: string
  email: string
}

// 使用
async function fetchUser(id: number): Promise<ApiResponse<User>> {
  const response = await fetch(`/api/users/${id}`)
  return response.json()
}

// 文章列表
interface Post {
  id: number
  title: string
  content: string
}

async function fetchPosts(): Promise<ApiResponse<Post[]>> {
  const response = await fetch('/api/posts')
  return response.json()
}

2. 条件类型(Conditional Types)

条件类型允许我们根据条件选择类型,类似于三元运算符。

基础条件类型

// T extends U ? X : Y
type IsString<T> = T extends string ? true : false

type A = IsString<string> // true
type B = IsString<number> // false

实用示例:提取函数返回类型

// 如果 T 是函数,提取返回类型;否则返回 never
type ReturnTypeOf<T> = T extends (...args: any[]) => infer R ? R : never

function getUser() {
  return { id: 1, name: "张三" }
}

type UserType = ReturnTypeOf<typeof getUser>
// 类型: { id: number; name: string }

分布式条件类型

// 从联合类型中排除 null 和 undefined
type NonNullable<T> = T extends null | undefined ? never : T

type A = NonNullable<string | null | undefined> // string
type B = NonNullable<number | null> // number

3. 映射类型(Mapped Types)

映射类型允许我们基于旧类型创建新类型。

Partial - 所有属性变为可选

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

// 内置的 Partial 实现
type MyPartial<T> = {
  [P in keyof T]?: T[P]
}

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

// 实际应用:更新用户信息
function updateUser(id: number, updates: Partial<User>) {
  // 只需要传递要更新的字段
}

updateUser(1, { name: "李四" }) // ✅
updateUser(2, { email: "test@example.com" }) // ✅

Required - 所有属性变为必需

interface Config {
  host?: string
  port?: number
  timeout?: number
}

type RequiredConfig = Required<Config>
// 等同于:
// {
//   host: string
//   port: number
//   timeout: number
// }

Readonly - 所有属性变为只读

interface User {
  id: number
  name: string
}

type ReadonlyUser = Readonly<User>
// 等同于:
// {
//   readonly id: number
//   readonly name: string
// }

const user: ReadonlyUser = { id: 1, name: "张三" }
// user.name = "李四" // ❌ 错误:无法分配到只读属性

Pick - 选择部分属性

interface User {
  id: number
  name: string
  email: string
  password: string
  createdAt: Date
}

// 只选择公开的用户信息
type PublicUser = Pick<User, 'id' | 'name' | 'email'>
// 等同于:
// {
//   id: number
//   name: string
//   email: string
// }

Omit - 排除部分属性

// 排除敏感信息
type SafeUser = Omit<User, 'password'>
// 等同于:
// {
//   id: number
//   name: string
//   email: string
//   createdAt: Date
// }

4. 实用工具类型

Record - 创建对象类型

// Record<Keys, Type>
type PageInfo = {
  title: string
  url: string
}

type Pages = 'home' | 'about' | 'contact'

const pages: Record<Pages, PageInfo> = {
  home: { title: '首页', url: '/' },
  about: { title: '关于', url: '/about' },
  contact: { title: '联系', url: '/contact' },
}

Extract - 提取联合类型的子集

type T1 = Extract<'a' | 'b' | 'c', 'a' | 'f'> // 'a'
type T2 = Extract<string | number | (() => void), Function> // () => void

Exclude - 排除联合类型的子集

type T1 = Exclude<'a' | 'b' | 'c', 'a' | 'b'> // 'c'
type T2 = Exclude<string | number | boolean, boolean> // string | number

5. 高级实战案例

案例 1:类型安全的事件系统

// 定义事件映射
interface EventMap {
  'user:login': { userId: number; timestamp: Date }
  'user:logout': { userId: number }
  'post:create': { postId: number; title: string }
  'post:delete': { postId: number }
}

// 类型安全的事件发射器
class TypedEventEmitter {
  private listeners: {
    [K in keyof EventMap]?: Array<(data: EventMap[K]) => void>
  } = {}

  // 监听事件
  on<K extends keyof EventMap>(
    event: K,
    callback: (data: EventMap[K]) => void
  ) {
    if (!this.listeners[event]) {
      this.listeners[event] = []
    }
    this.listeners[event]!.push(callback)
  }

  // 触发事件
  emit<K extends keyof EventMap>(event: K, data: EventMap[K]) {
    const callbacks = this.listeners[event]
    if (callbacks) {
      callbacks.forEach(callback => callback(data))
    }
  }
}

// 使用
const emitter = new TypedEventEmitter()

// ✅ 类型正确
emitter.on('user:login', (data) => {
  console.log(`用户 ${data.userId} 登录`)
})

// ✅ 类型正确
emitter.emit('user:login', {
  userId: 1,
  timestamp: new Date()
})

// ❌ 类型错误:缺少 timestamp
// emitter.emit('user:login', { userId: 1 })

案例 2:深度只读类型

// 递归地将所有属性变为只读
type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends object
    ? DeepReadonly<T[P]>
    : T[P]
}

interface Config {
  database: {
    host: string
    port: number
    credentials: {
      username: string
      password: string
    }
  }
  cache: {
    enabled: boolean
    ttl: number
  }
}

type ReadonlyConfig = DeepReadonly<Config>

const config: ReadonlyConfig = {
  database: {
    host: 'localhost',
    port: 5432,
    credentials: {
      username: 'admin',
      password: 'secret'
    }
  },
  cache: {
    enabled: true,
    ttl: 3600
  }
}

// ❌ 所有层级都是只读的
// config.database.host = 'newhost'
// config.database.credentials.password = 'newpass'

案例 3:类型安全的 API 客户端

// API 端点定义
interface ApiEndpoints {
  '/users': {
    GET: { response: User[] }
    POST: { body: Omit<User, 'id'>; response: User }
  }
  '/users/:id': {
    GET: { params: { id: number }; response: User }
    PUT: { params: { id: number }; body: Partial<User>; response: User }
    DELETE: { params: { id: number }; response: void }
  }
  '/posts': {
    GET: { query: { page?: number; limit?: number }; response: Post[] }
    POST: { body: Omit<Post, 'id'>; response: Post }
  }
}

// 类型安全的 API 客户端
class ApiClient {
  async request<
    Path extends keyof ApiEndpoints,
    Method extends keyof ApiEndpoints[Path]
  >(
    path: Path,
    method: Method,
    options?: ApiEndpoints[Path][Method] extends { body: infer B }
      ? { body: B }
      : ApiEndpoints[Path][Method] extends { params: infer P }
      ? { params: P }
      : ApiEndpoints[Path][Method] extends { query: infer Q }
      ? { query: Q }
      : never
  ): Promise<
    ApiEndpoints[Path][Method] extends { response: infer R } ? R : never
  > {
    // 实现细节...
    return {} as any
  }
}

// 使用
const api = new ApiClient()

// ✅ 类型安全
const users = await api.request('/users', 'GET')
// users 类型: User[]

// ✅ 类型安全
const newUser = await api.request('/users', 'POST', {
  body: { name: '张三', email: 'zhang@example.com' }
})
// newUser 类型: User

// ❌ 类型错误:缺少必需的 body
// await api.request('/users', 'POST')

6. 最佳实践

1. 优先使用类型推断

// ❌ 不必要的类型注解
const name: string = "张三"
const age: number = 25

// ✅ 让 TypeScript 推断
const name = "张三" // 推断为 string
const age = 25 // 推断为 number

2. 使用 const 断言

// ❌ 类型过于宽泛
const config = {
  host: 'localhost',
  port: 3000
}
// config 类型: { host: string; port: number }

// ✅ 使用 const 断言
const config = {
  host: 'localhost',
  port: 3000
} as const
// config 类型: { readonly host: "localhost"; readonly port: 3000 }

3. 避免使用 any

// ❌ 失去类型安全
function processData(data: any) {
  return data.value
}

// ✅ 使用泛型或 unknown
function processData<T>(data: T) {
  return data
}

// 或者
function processData(data: unknown) {
  if (typeof data === 'object' && data !== null && 'value' in data) {
    return (data as { value: any }).value
  }
}

4. 使用严格模式

// tsconfig.json
{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictPropertyInitialization": true
  }
}

总结

TypeScript 的高级类型系统为我们提供了强大的工具来编写类型安全的代码。关键要点:

  1. 善用泛型,编写可重用的代码
  2. 掌握条件类型,实现复杂的类型逻辑
  3. 活用映射类型,转换现有类型
  4. 使用工具类型,简化常见操作
  5. 遵循最佳实践,保持代码质量

通过这些技巧,你将能够构建更加健壮、可维护的 TypeScript 应用。

相关资源


标签: #TypeScript #类型系统 #泛型 #类型安全 #最佳实践

发布日期: 2025-02-08

作者: 君知博客团队