TypeScript 实用技巧
TypeScript, 技术, 编程

TypeScript 实用技巧

分享一些在实际开发中非常有用的 TypeScript 技巧和模式。

0次点击8分钟阅读

TypeScript 让代码更安全

TypeScript 为 JavaScript 添加了类型系统,让代码更加健壮和可维护。

实用技巧

1. 类型收窄

1function processValue(value: string | number) {
2  if (typeof value === 'string') {
3    // 这里 value 被推断为 string
4    return value.toUpperCase()
5  }
6  // 这里 value 被推断为 number
7  return value.toFixed(2)
8}

2. 泛型约束

1interface HasLength {
2  length: number
3}
4
5function logLength<T extends HasLength>(arg: T): T {
6  console.log(arg.length)
7  return arg
8}

3. 工具类型

TypeScript 提供了许多内置工具类型:

1// Partial - 使所有属性可选
2type PartialUser = Partial<User>
3
4// Pick - 选择特定属性
5type UserPreview = Pick<User, 'id' | 'name'>
6
7// Omit - 排除特定属性
8type UserWithoutPassword = Omit<User, 'password'>
9
10// Record - 创建键值对类型
11type PageConfig = Record<string, { title: string; description: string }>

4. 条件类型

1type IsString<T> = T extends string ? true : false
2
3type Result1 = IsString<string>  // true
4type Result2 = IsString<number>  // false

5. 映射类型

1type Readonly<T> = {
2  readonly [P in keyof T]: T[P]
3}
4
5type Optional<T> = {
6  [P in keyof T]?: T[P]
7}

最佳实践

  1. 启用严格模式: 在 tsconfig.json 中设置 "strict": true
  2. 避免使用 any: 尽量使用 unknown 或具体类型
  3. 利用类型推断: 让 TypeScript 自动推断类型
  4. 使用接口定义对象: 接口更适合定义对象形状
  5. 使用 const assertions: 使用 as const 创建只读的字面量类型

示例:类型安全的 API 调用

1interface ApiResponse<T> {
2  data: T
3  error?: string
4  status: number
5}
6
7async function fetchData<T>(url: string): Promise<ApiResponse<T>> {
8  try {
9    const response = await fetch(url)
10    const data = await response.json()
11    return {
12      data,
13      status: response.status,
14    }
15  } catch (error) {
16    return {
17      data: {} as T,
18      error: error.message,
19      status: 500,
20    }
21  }
22}
23
24// 使用
25interface User {
26  id: number
27  name: string
28}
29
30const result = await fetchData<User>('/api/user')
31// result.data 的类型是 User

高级类型技巧

模板字面量类型

1type EventName = 'click' | 'scroll' | 'mousemove'
2type EventHandler = `on${Capitalize<EventName>}`
3// 结果: 'onClick' | 'onScroll' | 'onMousemove'
4
5type Route = '/user' | '/product' | '/admin'
6type FullRoute = `https://api.example.com${Route}`
7// 结果: 'https://api.example.com/user' | ...

递归类型

1type DeepPartial<T> = {
2  [P in keyof T]?: T[P] extends object
3    ? DeepPartial<T[P]>
4    : T[P]
5}
6
7interface Config {
8  server: {
9    host: string
10    port: number
11    ssl: {
12      enabled: boolean
13      cert: string
14    }
15  }
16}
17
18// 所有属性都变为可选,包括嵌套属性
19type PartialConfig = DeepPartial<Config>

联合类型辨别

1type Success = {
2  status: 'success'
3  data: string
4}
5
6type Error = {
7  status: 'error'
8  error: string
9}
10
11type Result = Success | Error
12
13function handleResult(result: Result) {
14  if (result.status === 'success') {
15    // TypeScript 知道这里是 Success 类型
16    console.log(result.data)
17  } else {
18    // TypeScript 知道这里是 Error 类型
19    console.log(result.error)
20  }
21}

infer 关键字

1// 提取 Promise 的返回类型
2type UnwrapPromise<T> = T extends Promise<infer R> ? R : T
3
4type A = UnwrapPromise<Promise<string>>  // string
5type B = UnwrapPromise<number>          // number
6
7// 提取函数返回类型
8type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never
9
10// 提取数组元素类型
11type ArrayElement<T> = T extends (infer E)[] ? E : never
12
13type Items = ArrayElement<string[]>  // string

类型守卫进阶

自定义类型守卫

1interface Fish {
2  swim: () => void
3}
4
5interface Bird {
6  fly: () => void
7}
8
9// 自定义类型守卫
10function isFish(pet: Fish | Bird): pet is Fish {
11  return (pet as Fish).swim !== undefined
12}
13
14function move(pet: Fish | Bird) {
15  if (isFish(pet)) {
16    pet.swim()
17  } else {
18    pet.fly()
19  }
20}

断言函数

1function assert(condition: boolean, message: string): asserts condition {
2  if (!condition) {
3    throw new Error(message)
4  }
5}
6
7function processValue(value: string | null) {
8  assert(value !== null, 'Value cannot be null')
9  // TypeScript 知道这里 value 是 string 类型
10  console.log(value.toUpperCase())
11}
12
13// 类型断言函数
14function assertIsString(value: unknown): asserts value is string {
15  if (typeof value !== 'string') {
16    throw new Error('Value must be a string')
17  }
18}

实用类型模式

构建器模式

1class QueryBuilder<T> {
2  private filters: Array<(item: T) => boolean> = []
3  private sorters: Array<(a: T, b: T) => number> = []
4
5  where(predicate: (item: T) => boolean): this {
6    this.filters.push(predicate)
7    return this
8  }
9
10  sortBy(compareFn: (a: T, b: T) => number): this {
11    this.sorters.push(compareFn)
12    return this
13  }
14
15  execute(data: T[]): T[] {
16    let result = data
17
18    // 应用过滤
19    for (const filter of this.filters) {
20      result = result.filter(filter)
21    }
22
23    // 应用排序
24    for (const sorter of this.sorters) {
25      result = result.sort(sorter)
26    }
27
28    return result
29  }
30}
31
32// 使用
33interface User {
34  id: number
35  name: string
36  age: number
37}
38
39const users: User[] = [...]
40
41const result = new QueryBuilder<User>()
42  .where(u => u.age > 18)
43  .where(u => u.name.startsWith('A'))
44  .sortBy((a, b) => a.age - b.age)
45  .execute(users)

状态机类型

1type State =
2  | { status: 'idle' }
3  | { status: 'loading' }
4  | { status: 'success'; data: string }
5  | { status: 'error'; error: Error }
6
7class StateMachine {
8  private state: State = { status: 'idle' }
9
10  start() {
11    if (this.state.status !== 'idle') {
12      throw new Error('Already started')
13    }
14    this.state = { status: 'loading' }
15  }
16
17  success(data: string) {
18    if (this.state.status !== 'loading') {
19      throw new Error('Not loading')
20    }
21    this.state = { status: 'success', data }
22  }
23
24  fail(error: Error) {
25    if (this.state.status !== 'loading') {
26      throw new Error('Not loading')
27    }
28    this.state = { status: 'error', error }
29  }
30
31  getState(): State {
32    return this.state
33  }
34}

类型安全的事件系统

1type EventMap = {
2  'user:login': { userId: string; timestamp: number }
3  'user:logout': { userId: string }
4  'data:update': { dataId: string; changes: object }
5}
6
7class TypedEventEmitter {
8  private listeners: {
9    [K in keyof EventMap]?: Array<(data: EventMap[K]) => void>
10  } = {}
11
12  on<K extends keyof EventMap>(
13    event: K,
14    handler: (data: EventMap[K]) => void
15  ): void {
16    if (!this.listeners[event]) {
17      this.listeners[event] = []
18    }
19    this.listeners[event]!.push(handler)
20  }
21
22  emit<K extends keyof EventMap>(event: K, data: EventMap[K]): void {
23    const handlers = this.listeners[event] || []
24    for (const handler of handlers) {
25      handler(data)
26    }
27  }
28}
29
30// 使用
31const emitter = new TypedEventEmitter()
32
33emitter.on('user:login', (data) => {
34  // data 的类型是 { userId: string; timestamp: number }
35  console.log(`User ${data.userId} logged in at ${data.timestamp}`)
36})
37
38emitter.emit('user:login', {
39  userId: '123',
40  timestamp: Date.now()
41})

装饰器和元数据

类装饰器

1function Singleton<T extends { new (...args: any[]): {} }>(constructor: T) {
2  let instance: T | null = null
3
4  return class extends constructor {
5    constructor(...args: any[]) {
6      if (instance) {
7        return instance as any
8      }
9      super(...args)
10      instance = this as any
11    }
12  }
13}
14
15@Singleton
16class Database {
17  connect() {
18    console.log('Connecting to database...')
19  }
20}
21
22const db1 = new Database()
23const db2 = new Database()
24console.log(db1 === db2)  // true

方法装饰器

1function Log(
2  target: any,
3  propertyKey: string,
4  descriptor: PropertyDescriptor
5) {
6  const originalMethod = descriptor.value
7
8  descriptor.value = function (...args: any[]) {
9    console.log(`Calling ${propertyKey} with args:`, args)
10    const result = originalMethod.apply(this, args)
11    console.log(`Result:`, result)
12    return result
13  }
14
15  return descriptor
16}
17
18class Calculator {
19  @Log
20  add(a: number, b: number): number {
21    return a + b
22  }
23}
24
25const calc = new Calculator()
26calc.add(2, 3)  // 会打印日志

属性装饰器

1function Validate(validator: (value: any) => boolean) {
2  return function (target: any, propertyKey: string) {
3    let value: any
4
5    const getter = function () {
6      return value
7    }
8
9    const setter = function (newValue: any) {
10      if (!validator(newValue)) {
11        throw new Error(`Invalid value for ${propertyKey}`)
12      }
13      value = newValue
14    }
15
16    Object.defineProperty(target, propertyKey, {
17      get: getter,
18      set: setter,
19      enumerable: true,
20      configurable: true
21    })
22  }
23}
24
25class User {
26  @Validate((value) => typeof value === 'string' && value.length > 0)
27  name: string = ''
28
29  @Validate((value) => typeof value === 'number' && value >= 0)
30  age: number = 0
31}

类型编程技巧

对象键的类型操作

1// 获取所有值为特定类型的键
2type FilterByType<T, U> = {
3  [K in keyof T]: T[K] extends U ? K : never
4}[keyof T]
5
6interface Person {
7  name: string
8  age: number
9  email: string
10  isAdmin: boolean
11}
12
13type StringKeys = FilterByType<Person, string>
14// 结果: 'name' | 'email'
15
16type NumberKeys = FilterByType<Person, number>
17// 结果: 'age'

深度只读

1type DeepReadonly<T> = {
2  readonly [P in keyof T]: T[P] extends object
3    ? DeepReadonly<T[P]>
4    : T[P]
5}
6
7interface MutableConfig {
8  server: {
9    host: string
10    port: number
11    options: {
12      timeout: number
13      retry: boolean
14    }
15  }
16}
17
18type ImmutableConfig = DeepReadonly<MutableConfig>
19// 所有属性和嵌套属性都变为只读

函数重载类型

1function createElement(tag: 'div'): HTMLDivElement
2function createElement(tag: 'span'): HTMLSpanElement
3function createElement(tag: 'a'): HTMLAnchorElement
4function createElement(tag: string): HTMLElement {
5  return document.createElement(tag)
6}
7
8// TypeScript 会根据参数自动推断返回类型
9const div = createElement('div')    // HTMLDivElement
10const span = createElement('span')  // HTMLSpanElement
11const a = createElement('a')        // HTMLAnchorElement

性能优化

类型计算复杂度

1// ❌ 避免过度复杂的递归类型
2type Bad<T> = T extends any
3  ? T extends any
4    ? T extends any
5      ? T
6      : never
7    : never
8  : never
9
10// ✅ 保持类型简洁
11type Good<T> = T

使用类型别名

1// ❌ 重复的复杂类型
2function processA(data: { id: string; name: string; metadata: Record<string, unknown> }[]) {}
3function processB(data: { id: string; name: string; metadata: Record<string, unknown> }[]) {}
4
5// ✅ 使用类型别名
6type DataItem = {
7  id: string
8  name: string
9  metadata: Record<string, unknown>
10}
11
12function processA(data: DataItem[]) {}
13function processB(data: DataItem[]) {}

调试技巧

检查类型

1// 使用注释查看推断的类型
2type Test = { foo: string }
3//   ^? Test
4
5// 使用 Extract 和 Exclude
6type A = Extract<'a' | 'b' | 'c', 'a' | 'b'>  // 'a' | 'b'
7type B = Exclude<'a' | 'b' | 'c', 'a'>        // 'b' | 'c'
8
9// 类型断言用于调试
10type Debug<T> = { [K in keyof T]: T[K] } & {}
11type Example = Debug<{ a: string; b: number }>

类型错误定位

1// 使用 never 捕获错误
2type AssertTrue<T extends true> = T
3type Test1 = AssertTrue<true>   // ✓
4type Test2 = AssertTrue<false>  // ✗ Type 'false' does not satisfy...
5
6// 类型分支调试
7type CheckType<T> = T extends string
8  ? 'is string'
9  : T extends number
10  ? 'is number'
11  : 'is other'
12
13type Test = CheckType<'hello'>  // 'is string'

实战:类型安全的 API 客户端

1interface APIEndpoints {
2  '/users': {
3    GET: {
4      response: { users: User[] }
5    }
6    POST: {
7      body: { name: string; email: string }
8      response: { user: User }
9    }
10  }
11  '/users/:id': {
12    GET: {
13      params: { id: string }
14      response: { user: User }
15    }
16    PUT: {
17      params: { id: string }
18      body: Partial<User>
19      response: { user: User }
20    }
21    DELETE: {
22      params: { id: string }
23      response: { success: boolean }
24    }
25  }
26}
27
28class APIClient {
29  async request<
30    Path extends keyof APIEndpoints,
31    Method extends keyof APIEndpoints[Path]
32  >(
33    method: Method,
34    path: Path,
35    config: APIEndpoints[Path][Method] extends { body: infer B }
36      ? { body: B } & (APIEndpoints[Path][Method] extends { params: infer P } ? { params: P } : {})
37      : APIEndpoints[Path][Method] extends { params: infer P }
38      ? { params: P }
39      : {}
40  ): Promise<
41    APIEndpoints[Path][Method] extends { response: infer R } ? R : never
42  > {
43    // 实现...
44    return {} as any
45  }
46}
47
48// 使用
49const client = new APIClient()
50
51// TypeScript 会自动推断参数和返回类型
52const users = await client.request('GET', '/users', {})
53const user = await client.request('GET', '/users/:id', {
54  params: { id: '123' }
55})
56const newUser = await client.request('POST', '/users', {
57  body: { name: 'John', email: 'john@example.com' }
58})

配置优化

tsconfig.json 推荐配置

1{
2  "compilerOptions": {
3    // 严格模式
4    "strict": true,
5    "noUncheckedIndexedAccess": true,
6    "noImplicitOverride": true,
7    "noPropertyAccessFromIndexSignature": true,
8
9    // 模块解析
10    "moduleResolution": "bundler",
11    "resolveJsonModule": true,
12    "allowSyntheticDefaultImports": true,
13    "esModuleInterop": true,
14
15    // 输出
16    "target": "ES2022",
17    "module": "ESNext",
18    "lib": ["ES2022", "DOM", "DOM.Iterable"],
19
20    // 路径映射
21    "baseUrl": ".",
22    "paths": {
23      "~/*": ["./src/*"]
24    },
25
26    // 开发体验
27    "skipLibCheck": true,
28    "forceConsistentCasingInFileNames": true,
29    "incremental": true
30  }
31}

总结

TypeScript 的类型系统虽然有一定学习曲线,但掌握这些技巧后,能显著提升代码质量和开发体验:

  1. 基础技巧:类型收窄、泛型约束、工具类型
  2. 高级类型:模板字面量、递归类型、条件类型
  3. 类型守卫:自定义守卫、断言函数
  4. 实用模式:构建器、状态机、事件系统
  5. 类型编程:类型计算、深度操作
  6. 性能优化:减少复杂度、使用别名
  7. 调试技巧:类型检查、错误定位
  8. 实战应用:类型安全的 API、表单验证

持续学习和实践这些技巧,你将能够编写更安全、更可维护的 TypeScript 代码。

相关文章