Skip to content

TypeScript 进阶教程

本教程将深入探讨 TypeScript 的高级特性和最佳实践,帮助您成为 TypeScript 专家。

高级类型

联合类型和交叉类型

typescript
// 联合类型
type Status = 'loading' | 'success' | 'error'
type ID = string | number

// 交叉类型
interface User {
  name: string
  email: string
}

interface Admin {
  permissions: string[]
}

type AdminUser = User & Admin

const adminUser: AdminUser = {
  name: 'John',
  email: 'john@example.com',
  permissions: ['read', 'write', 'delete']
}

条件类型

typescript
// 基础条件类型
type IsString<T> = T extends string ? true : false

type Test1 = IsString<string>  // true
type Test2 = IsString<number>  // false

// 实用的条件类型
type NonNullable<T> = T extends null | undefined ? never : T
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any

// 分布式条件类型
type ToArray<T> = T extends any ? T[] : never
type StrArrOrNumArr = ToArray<string | number>  // string[] | number[]

映射类型

typescript
// 基础映射类型
type Readonly<T> = {
  readonly [P in keyof T]: T[P]
}

type Partial<T> = {
  [P in keyof T]?: T[P]
}

// 自定义映射类型
type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
}

interface Person {
  name: string
  age: number
}

type PersonGetters = Getters<Person>
// {
//   getName: () => string
//   getAge: () => number
// }

模板字面量类型

typescript
// 基础模板字面量类型
type Greeting = `Hello ${string}`
type EmailLocaleIDs = `${string}_${string}`

// 结合映射类型
type EventNames<T extends string> = `${T}Changed`
type PersonEvents = EventNames<'name' | 'age'>  // 'nameChanged' | 'ageChanged'

// 实用示例:CSS 属性
type CSSProperties = {
  [K in 
    | 'color' 
    | 'background-color' 
    | 'font-size'
    as `--${K}`
  ]?: string
}

const styles: CSSProperties = {
  '--color': 'red',
  '--background-color': 'blue',
  '--font-size': '16px'
}

泛型进阶

泛型约束

typescript
// 基础约束
interface Lengthwise {
  length: number
}

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

// 使用类型参数约束
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key]
}

const person = { name: 'John', age: 30 }
const name = getProperty(person, 'name')  // string
const age = getProperty(person, 'age')    // number

泛型工具类型

typescript
// 自定义工具类型
type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P]
}

type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P]
}

// 函数重载的泛型
function createElement<T extends keyof HTMLElementTagNameMap>(
  tag: T
): HTMLElementTagNameMap[T]
function createElement<T extends HTMLElement>(
  tag: string
): T
function createElement(tag: string): HTMLElement {
  return document.createElement(tag)
}

const div = createElement('div')      // HTMLDivElement
const span = createElement('span')    // HTMLSpanElement
const custom = createElement<HTMLElement>('custom')  // HTMLElement

装饰器

类装饰器

typescript
// 类装饰器
function sealed(constructor: Function) {
  Object.seal(constructor)
  Object.seal(constructor.prototype)
}

function classDecorator<T extends { new (...args: any[]): {} }>(
  constructor: T
) {
  return class extends constructor {
    newProperty = 'new property'
    hello = 'override'
  }
}

@sealed
@classDecorator
class BugReport {
  type = 'report'
  title: string

  constructor(t: string) {
    this.title = t
  }
}

方法装饰器

typescript
// 方法装饰器
function enumerable(value: boolean) {
  return function (
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    descriptor.enumerable = value
  }
}

function log(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  const originalMethod = descriptor.value

  descriptor.value = function (...args: any[]) {
    console.log(`Calling ${propertyKey} with args:`, args)
    const result = originalMethod.apply(this, args)
    console.log(`Result:`, result)
    return result
  }
}

class Calculator {
  @enumerable(false)
  @log
  add(a: number, b: number): number {
    return a + b
  }
}

属性装饰器

typescript
// 属性装饰器
function format(formatString: string) {
  return function (target: any, propertyKey: string) {
    let value: string

    const getter = () => value
    const setter = (newVal: string) => {
      value = formatString.replace('%s', newVal)
    }

    Object.defineProperty(target, propertyKey, {
      get: getter,
      set: setter,
      enumerable: true,
      configurable: true
    })
  }
}

class Greeter {
  @format('Hello, %s!')
  greeting: string

  constructor(message: string) {
    this.greeting = message
  }
}

const greeter = new Greeter('world')
console.log(greeter.greeting)  // "Hello, world!"

模块系统

命名空间

typescript
// 命名空间
namespace Validation {
  export interface StringValidator {
    isAcceptable(s: string): boolean
  }

  const lettersRegexp = /^[A-Za-z]+$/
  const numberRegexp = /^[0-9]+$/

  export class LettersOnlyValidator implements StringValidator {
    isAcceptable(s: string) {
      return lettersRegexp.test(s)
    }
  }

  export class ZipCodeValidator implements StringValidator {
    isAcceptable(s: string) {
      return s.length === 5 && numberRegexp.test(s)
    }
  }
}

// 使用命名空间
const validators: { [s: string]: Validation.StringValidator } = {}
validators['ZIP code'] = new Validation.ZipCodeValidator()
validators['Letters only'] = new Validation.LettersOnlyValidator()

模块声明

typescript
// 声明外部模块
declare module 'lodash' {
  export function chunk<T>(array: T[], size?: number): T[][]
  export function debounce<T extends (...args: any[]) => any>(
    func: T,
    wait?: number,
    options?: object
  ): T
}

// 全局声明
declare global {
  interface Window {
    myGlobalFunction: (name: string) => void
  }
}

// 模块扩展
declare module 'vue' {
  interface ComponentCustomProperties {
    $myGlobalProperty: string
  }
}

实用技巧

类型守卫

typescript
// 用户定义的类型守卫
function isString(value: any): value is string {
  return typeof value === 'string'
}

function isNumber(value: any): value is number {
  return typeof value === 'number'
}

// 使用类型守卫
function processValue(value: string | number) {
  if (isString(value)) {
    // value 的类型被缩小为 string
    console.log(value.toUpperCase())
  } else if (isNumber(value)) {
    // value 的类型被缩小为 number
    console.log(value.toFixed(2))
  }
}

// 判别联合类型
interface Bird {
  type: 'bird'
  flyingSpeed: number
}

interface Horse {
  type: 'horse'
  runningSpeed: number
}

type Animal = Bird | Horse

function moveAnimal(animal: Animal) {
  switch (animal.type) {
    case 'bird':
      console.log('Flying at speed: ' + animal.flyingSpeed)
      break
    case 'horse':
      console.log('Running at speed: ' + animal.runningSpeed)
      break
  }
}

索引签名和映射类型

typescript
// 索引签名
interface StringDictionary {
  [key: string]: string
}

interface NumberDictionary {
  [key: string]: number
  length: number    // 可以,length 是 number 类型
  name: string      // 错误,name 的类型与索引类型返回值的类型不匹配
}

// 高级映射类型
type Pick<T, K extends keyof T> = {
  [P in K]: T[P]
}

type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>

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

type PublicUser = Omit<User, 'password'>  // { id: number; name: string; email: string }
type UserCredentials = Pick<User, 'email' | 'password'>  // { email: string; password: string }

函数重载

typescript
// 函数重载
function reverse(x: string): string
function reverse(x: number): number
function reverse(x: boolean): boolean
function reverse(x: string | number | boolean): string | number | boolean {
  if (typeof x === 'string') {
    return x.split('').reverse().join('')
  } else if (typeof x === 'number') {
    return Number(x.toString().split('').reverse().join(''))
  } else {
    return !x
  }
}

const reversedString = reverse('hello')    // string
const reversedNumber = reverse(12345)      // number
const reversedBoolean = reverse(true)      // boolean

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

让我们创建一个类型安全的 API 客户端:

typescript
// types/api.ts
export interface User {
  id: number
  name: string
  email: string
  createdAt: string
}

export interface CreateUserRequest {
  name: string
  email: string
}

export interface UpdateUserRequest {
  name?: string
  email?: string
}

export interface ApiResponse<T> {
  data: T
  message: string
  success: boolean
}

export interface PaginatedResponse<T> {
  data: T[]
  total: number
  page: number
  limit: number
}

// api/client.ts
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE'

interface RequestConfig {
  method: HttpMethod
  headers?: Record<string, string>
  body?: any
}

class ApiClient {
  private baseURL: string
  private defaultHeaders: Record<string, string>

  constructor(baseURL: string) {
    this.baseURL = baseURL
    this.defaultHeaders = {
      'Content-Type': 'application/json'
    }
  }

  private async request<T>(
    endpoint: string, 
    config: RequestConfig
  ): Promise<ApiResponse<T>> {
    const url = `${this.baseURL}${endpoint}`
    const headers = { ...this.defaultHeaders, ...config.headers }

    const response = await fetch(url, {
      method: config.method,
      headers,
      body: config.body ? JSON.stringify(config.body) : undefined
    })

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`)
    }

    return response.json()
  }

  async get<T>(endpoint: string): Promise<ApiResponse<T>> {
    return this.request<T>(endpoint, { method: 'GET' })
  }

  async post<T, U = any>(
    endpoint: string, 
    data: U
  ): Promise<ApiResponse<T>> {
    return this.request<T>(endpoint, {
      method: 'POST',
      body: data
    })
  }

  async put<T, U = any>(
    endpoint: string, 
    data: U
  ): Promise<ApiResponse<T>> {
    return this.request<T>(endpoint, {
      method: 'PUT',
      body: data
    })
  }

  async delete<T>(endpoint: string): Promise<ApiResponse<T>> {
    return this.request<T>(endpoint, { method: 'DELETE' })
  }
}

// api/users.ts
export class UserService {
  constructor(private client: ApiClient) {}

  async getUsers(): Promise<ApiResponse<PaginatedResponse<User>>> {
    return this.client.get<PaginatedResponse<User>>('/users')
  }

  async getUser(id: number): Promise<ApiResponse<User>> {
    return this.client.get<User>(`/users/${id}`)
  }

  async createUser(data: CreateUserRequest): Promise<ApiResponse<User>> {
    return this.client.post<User, CreateUserRequest>('/users', data)
  }

  async updateUser(
    id: number, 
    data: UpdateUserRequest
  ): Promise<ApiResponse<User>> {
    return this.client.put<User, UpdateUserRequest>(`/users/${id}`, data)
  }

  async deleteUser(id: number): Promise<ApiResponse<void>> {
    return this.client.delete<void>(`/users/${id}`)
  }
}

// 使用示例
const apiClient = new ApiClient('https://api.example.com')
const userService = new UserService(apiClient)

async function example() {
  try {
    // 获取用户列表
    const usersResponse = await userService.getUsers()
    console.log(usersResponse.data.data) // User[]

    // 创建新用户
    const newUser = await userService.createUser({
      name: 'John Doe',
      email: 'john@example.com'
    })
    console.log(newUser.data) // User

    // 更新用户
    const updatedUser = await userService.updateUser(1, {
      name: 'Jane Doe'
    })
    console.log(updatedUser.data) // User

  } catch (error) {
    console.error('API Error:', error)
  }
}

性能优化

类型推断优化

typescript
// 避免过度注解
// ❌ 不好
const users: User[] = await getUsers()

// ✅ 好
const users = await getUsers() // TypeScript 可以推断类型

// 使用 const 断言
// ❌ 不好
const config = {
  apiUrl: 'https://api.example.com',
  timeout: 5000
} // 类型为 { apiUrl: string; timeout: number }

// ✅ 好
const config = {
  apiUrl: 'https://api.example.com',
  timeout: 5000
} as const // 类型为 { readonly apiUrl: "https://api.example.com"; readonly timeout: 5000 }

编译优化

json
// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "moduleResolution": "node",
    "strict": true,
    "skipLibCheck": true,
    "incremental": true,
    "tsBuildInfoFile": ".tsbuildinfo"
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

测试

类型测试

typescript
// 类型测试工具
type Expect<T extends true> = T
type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2
  ? true
  : false

// 测试用例
type cases = [
  Expect<Equal<Pick<User, 'name'>, { name: string }>>,
  Expect<Equal<Omit<User, 'id'>, { name: string; email: string; createdAt: string }>>,
]

// 运行时测试
import { describe, it, expect } from 'vitest'

describe('UserService', () => {
  it('should create user with correct type', async () => {
    const userService = new UserService(apiClient)
    const userData: CreateUserRequest = {
      name: 'Test User',
      email: 'test@example.com'
    }
    
    const response = await userService.createUser(userData)
    
    expect(response.data).toHaveProperty('id')
    expect(response.data).toHaveProperty('name', userData.name)
    expect(response.data).toHaveProperty('email', userData.email)
  })
})

最佳实践

1. 类型设计原则

typescript
// ✅ 使用联合类型而不是枚举
type Theme = 'light' | 'dark' | 'auto'

// ✅ 使用接口扩展
interface BaseEntity {
  id: string
  createdAt: Date
  updatedAt: Date
}

interface User extends BaseEntity {
  name: string
  email: string
}

// ✅ 使用泛型约束
function processItems<T extends { id: string }>(items: T[]): T[] {
  return items.filter(item => item.id.length > 0)
}

2. 错误处理

typescript
// 结果类型模式
type Result<T, E = Error> = 
  | { success: true; data: T }
  | { success: false; error: E }

async function safeApiCall<T>(
  apiCall: () => Promise<T>
): Promise<Result<T>> {
  try {
    const data = await apiCall()
    return { success: true, data }
  } catch (error) {
    return { 
      success: false, 
      error: error instanceof Error ? error : new Error(String(error))
    }
  }
}

// 使用
const result = await safeApiCall(() => userService.getUser(1))
if (result.success) {
  console.log(result.data) // User
} else {
  console.error(result.error) // Error
}

3. 配置管理

typescript
// 环境配置
interface Config {
  readonly apiUrl: string
  readonly timeout: number
  readonly retries: number
  readonly debug: boolean
}

const createConfig = (env: string): Config => {
  const baseConfig = {
    timeout: 5000,
    retries: 3,
    debug: false
  }

  switch (env) {
    case 'development':
      return {
        ...baseConfig,
        apiUrl: 'http://localhost:3000',
        debug: true
      }
    case 'production':
      return {
        ...baseConfig,
        apiUrl: 'https://api.production.com'
      }
    default:
      throw new Error(`Unknown environment: ${env}`)
  }
}

export const config = createConfig(process.env.NODE_ENV || 'development')

总结

TypeScript 进阶特性让我们能够:

  1. 类型安全 - 在编译时捕获错误
  2. 代码提示 - 更好的开发体验
  3. 重构支持 - 安全地重构代码
  4. 文档化 - 类型即文档
  5. 团队协作 - 统一的代码规范

下一步学习

  1. TypeScript 与 React
  2. TypeScript 与 Node.js
  3. TypeScript 编译器 API
  4. TypeScript 性能优化

参考资源


掌握这些 TypeScript 进阶特性,您就能写出更安全、更优雅的代码!🚀

vitepress开发指南