前端框架 精选推荐

TypeScript 类型系统深度进阶

HTMLPAGE 团队
13 分钟阅读

从基础到进阶完全讲解 TypeScript 的类型系统,包括泛型、条件类型、映射类型、装饰器等高级特性,以及实战中的最佳实践。

#TypeScript #类型系统 #泛型 #进阶

TypeScript 类型系统深度进阶

TypeScript 的强大之处在于其灵活而强大的类型系统。本文从进阶角度深度讲解,帮你写出更安全、更优雅的代码。

1. 泛型 (Generics) - 类型参数化

基础泛型函数

// ❌ 不好: 失去类型信息
function getFirstElement(arr: any[]): any {
  return arr[0]
}

// ✅ 好: 使用泛型,保留类型信息
function getFirstElement<T>(arr: T[]): T {
  return arr[0]
}

// 使用时自动推导类型
const numbers = [1, 2, 3]
const first = getFirstElement(numbers)  // first: number

泛型约束 (Generic Constraints)

// 定义约束条件
interface HasLength {
  length: number
}

// 泛型 T 必须有 length 属性
function getLength<T extends HasLength>(item: T): number {
  return item.length
}

getLength("hello")        // ✅ 字符串有 length
getLength([1, 2, 3])      // ✅ 数组有 length
getLength(123)            // ❌ 数字没有 length,编译错误

泛型工具类型

// 1. Partial - 使所有属性可选
interface User {
  name: string
  age: number
  email: string
}

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

// 2. Required - 使所有属性必需
type RequiredUser = Required<PartialUser>
// 回到完全必需的 User

// 3. Readonly - 使所有属性只读
type ReadonlyUser = Readonly<User>

// 4. Pick - 选择特定属性
type UserPreview = Pick<User, 'name' | 'email'>
// {
//   name: string
//   email: string
// }

// 5. Omit - 排除特定属性
type UserWithoutEmail = Omit<User, 'email'>
// {
//   name: string
//   age: number
// }

// 6. Record - 创建对象类型
type PageStatus = 'home' | 'about' | 'contact'
type PageConfig = Record<PageStatus, { title: string; url: string }>
// {
//   home: { title: string; url: string }
//   about: { title: string; url: string }
//   contact: { title: string; url: string }
// }

2. 条件类型 (Conditional Types) - 类型级 if-else

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

type A = IsString<'hello'>  // true
type B = IsString<123>       // false

// 实用例子: 获取数组元素类型
type Flatten<T> = T extends Array<infer U> ? U : T

type Str = Flatten<string[]>     // string
type Num = Flatten<number>       // number
type Arr = Flatten<Array<boolean>>  // boolean

// infer 关键字: 推导类型变量
type GetReturnType<T> = T extends (...args: any[]) => infer R ? R : never

type Func = (x: number) => string
type FuncReturn = GetReturnType<Func>  // string

// 条件类型分发 (Distributive)
type ToArray<T> = T extends any ? T[] : never

type StrOrNum = string | number
type StrOrNumArray = ToArray<StrOrNum>
// (string | number)[] 或
// string[] | number[] ?
// 答案是后者!条件类型在联合类型上分发

// 防止分发: 用 [] 包裹
type ToArray2<T> = [T] extends [any] ? T[] : never

3. 映射类型 (Mapped Types) - 批量生成类型

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

type User = {
  name: string
  age: number
}

type ReadonlyUser2 = Readonly2<User>
// {
//   readonly name: string
//   readonly age: number
// }

// 映射类型的实用例子

// 1. Getters: 为每个属性生成 getter 函数
type Getters<T> = {
  [K in keyof T as `get${Capitalize<K & string>}`]: () => T[K]
}

type GettersUser = Getters<User>
// {
//   getName: () => string
//   getAge: () => number
// }

// 2. Setters: 为每个属性生成 setter 函数
type Setters<T> = {
  [K in keyof T as `set${Capitalize<K & string>}`]: (value: T[K]) => void
}

// 3. Nullable: 所有属性可为 null
type Nullable<T> = {
  [K in keyof T]: T[K] | null
}

type NullableUser = Nullable<User>
// {
//   name: string | null
//   age: number | null
// }

// 4. ApiResponse: 包装 API 响应
type ApiResponse<T> = {
  [K in keyof T]: {
    data: T[K]
    loading: boolean
    error: Error | null
  }
}

type UserResponse = ApiResponse<User>
// {
//   name: { data: string; loading: boolean; error: Error | null }
//   age: { data: number; loading: boolean; error: Error | null }
// }

4. 装饰器 (Decorators) - 修改类和方法

类装饰器

// 基础类装饰器
function Observer(target: Function) {
  console.log(`Creating observer for ${target.name}`)
}

@Observer
class User {
  name: string = 'John'
  
  greet() {
    console.log(`Hello, ${this.name}`)
  }
}

// 装饰器工厂
function LogClass<T extends { new(...args: any[]): {} }>(constructor: T) {
  return class extends constructor {
    constructor(...args: any[]) {
      super(...args)
      console.log(`${constructor.name} 实例已创建`)
    }
  }
}

@LogClass
class UserWithLog {
  name = 'Alice'
}

new UserWithLog()  // 输出: UserWithLog 实例已创建

方法装饰器

// 方法装饰器: 添加日志
function LogMethod(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  const originalMethod = descriptor.value
  
  descriptor.value = function(...args: any[]) {
    console.log(`调用 ${propertyKey},参数:`, args)
    const result = originalMethod.apply(this, args)
    console.log(`${propertyKey} 返回:`, result)
    return result
  }
  
  return descriptor
}

class Calculator {
  @LogMethod
  add(a: number, b: number): number {
    return a + b
  }
}

const calc = new Calculator()
calc.add(2, 3)
// 输出:
// 调用 add,参数: [2, 3]
// add 返回: 5

属性装饰器

// 属性装饰器: 验证
function Validate(target: any, propertyKey: string) {
  let value: any
  
  const getter = () => value
  
  const setter = (newValue: any) => {
    if (typeof newValue !== 'string') {
      throw new Error(`${propertyKey} must be a string`)
    }
    value = newValue
  }
  
  Object.defineProperty(target, propertyKey, {
    get: getter,
    set: setter,
    enumerable: true,
    configurable: true
  })
}

class Person {
  @Validate
  name: string = ''
}

const person = new Person()
person.name = 'John'  // ✅ 成功
person.name = 123     // ❌ 抛出错误

5. 实战模式

模式 1: 深层类型安全的 API 客户端

// 定义 API 端点类型
interface ApiEndpoints {
  'users': {
    method: 'GET'
    params: { id: number }
    response: { name: string; age: number }
  }
  'posts': {
    method: 'POST'
    params: { title: string; content: string }
    response: { id: number }
  }
}

// 创建类型安全的 API 调用函数
type ApiCall<K extends keyof ApiEndpoints> = (
  key: K,
  params: ApiEndpoints[K]['params']
) => Promise<ApiEndpoints[K]['response']>

const api: ApiCall<any> = async (endpoint, params) => {
  const response = await fetch(`/api/${endpoint}`, {
    method: 'POST',
    body: JSON.stringify(params)
  })
  return response.json()
}

// 使用: 完全类型安全
async function getUser() {
  const user = await api('users', { id: 1 })
  console.log(user.name)  // ✅ 知道有 name 属性
  // console.log(user.title)  // ❌ 错误: 没有 title
}

模式 2: Store 类型推导

// 定义 Store
const store = {
  state: {
    user: { name: 'John', age: 30 },
    posts: [{ id: 1, title: 'Hello' }],
    ui: { theme: 'dark' }
  },
  getters: {
    userName() {
      return this.state.user.name
    },
    postCount() {
      return this.state.posts.length
    }
  }
}

// 自动推导所有键和类型
type StoreState = typeof store.state
type StateKeys = keyof StoreState  // 'user' | 'posts' | 'ui'
type UserState = StoreState['user']  // { name: string; age: number }

// 创建访问函数,类型完全推导
function useState<K extends keyof StoreState>(key: K) {
  return store.state[key]  // 返回类型自动推导为具体类型
}

const user = useState('user')  // { name: string; age: number }
const posts = useState('posts')  // { id: number; title: string }[]

模式 3: 响应式代理

// 创建响应式对象的类型安全代理
function reactive<T extends object>(target: T): T {
  return new Proxy(target, {
    get(obj, prop) {
      console.log(`访问属性: ${String(prop)}`)
      return obj[prop as keyof T]
    },
    set(obj, prop, value) {
      console.log(`设置 ${String(prop)} = ${value}`)
      obj[prop as keyof T] = value
      return true
    }
  })
}

const user = reactive({ name: 'John', age: 30 })
user.name = 'Alice'  // ✅ 有完整的类型检查
// user.email = 'test@com'  // ❌ 错误: 没有 email 属性

6. 类型检查最佳实践

优先使用联合类型而不是 any

// ❌ 避免
function process(value: any) {
  return value.toUpperCase()
}

// ✅ 改为
function process(value: string | string[]) {
  if (Array.isArray(value)) {
    return value.map(v => v.toUpperCase())
  }
  return value.toUpperCase()
}

使用类型守卫

// 类型谓词
function isString(value: any): value is string {
  return typeof value === 'string'
}

// 使用
function process(value: string | number) {
  if (isString(value)) {
    console.log(value.toUpperCase())  // 此时 TS 知道是 string
  } else {
    console.log(value.toFixed(2))  // 此时 TS 知道是 number
  }
}

严格的 null 检查

// 启用 tsconfig.json: "strictNullChecks": true

interface User {
  name: string
  email?: string  // 可选
}

function sendEmail(user: User) {
  // ❌ 错误: email 可能是 undefined
  // console.log(user.email.length)
  
  // ✅ 正确: 先检查
  if (user.email) {
    console.log(user.email.length)
  }
  
  // 或使用可选链
  console.log(user.email?.length)
}

TypeScript 配置最佳实践

{
  "compilerOptions": {
    // 严格模式
    "strict": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictBindCallApply": true,
    "strictPropertyInitialization": true,
    "noImplicitThis": true,
    
    // 模块和导出
    "module": "ESNext",
    "target": "ES2020",
    "moduleResolution": "node",
    
    // 类型检查
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "noImplicitAny": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true
  }
}

常见错误和解决方案

问题原因解决方案
"Type '...' is not assignable to type '...'"类型不兼容使用 as const 或明确类型
"Property '...' does not exist"属性不存在检查对象类型定义
"Cannot invoke an expression"调用非函数检查函数类型定义
"Object is possibly 'undefined'"空值检查启用 strictNullChecks
"Argument is not assignable"参数类型错误使用类型守卫或断言

总结:从编程到 TypeScript

// 第一阶段: 基础类型
let name: string = "John"
let age: number = 30

// 第二阶段: 接口和类型别名
interface User {
  name: string
  age: number
}

// 第三阶段: 泛型
function getProperty<T, K extends keyof T>(obj: T, key: K) {
  return obj[key]
}

// 第四阶段: 条件类型
type IsArray<T> = T extends Array<any> ? true : false

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

// 掌握这些,你就是 TypeScript 高手!

相关资源