TypeScript 高级教程 — 类型体操

前置要求:TypeScript基础
学习时长:约2-3天
适用场景:大型项目、类型安全、库开发

一、高级类型

1.1 联合类型与交叉类型

// 联合类型:可以是多种类型之一
type StringOrNumber = string | number
type Status = 'active' | 'inactive' | 'banned'

// 交叉类型:合并多个类型
type UserWithPost = User & Post
type AdminUser = User & { role: 'admin'; permissions: string[] }

// 使用
function format(value: StringOrNumber): string {
  if (typeof value === 'string') {
    return value.toUpperCase()
  }
  return value.toFixed(2)
}

1.2 类型守卫

// typeof守卫
function isString(value: unknown): value is string {
  return typeof value === 'string'
}

// instanceof守卫
function isError(value: unknown): value is Error {
  return value instanceof Error
}

// 自定义类型守卫
interface Cat { meow(): void }
interface Dog { bark(): void }

function isCat(animal: Cat | Dog): animal is Cat {
  return (animal as Cat).meow !== undefined
}

// 使用
function makeSound(animal: Cat | Dog) {
  if (isCat(animal)) {
    animal.meow()  // TypeScript知道这是Cat
  } else {
    animal.bark()  // TypeScript知道这是Dog
  }
}

1.3 可辨识联合

// 通过共同字段区分类型
interface Circle {
  kind: 'circle'
  radius: number
}

interface Rectangle {
  kind: 'rectangle'
  width: number
  height: number
}

interface Triangle {
  kind: 'triangle'
  base: number
  height: number
}

type Shape = Circle | Rectangle | Triangle

function area(shape: Shape): number {
  switch (shape.kind) {
    case 'circle':
      return Math.PI * shape.radius ** 2
    case 'rectangle':
      return shape.width * shape.height
    case 'triangle':
      return (shape.base * shape.height) / 2
  }
}


二、泛型高级

2.1 泛型约束

// 约束泛型必须有某些属性
interface HasId {
  id: number
}

function findById<T extends HasId>(items: T[], id: number): T | undefined {
  return items.find(item => item.id === id)
}

// 约束keyof
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key]
}

const user = { name: '张三', age: 25 }
getProperty(user, 'name')  // OK
// getProperty(user, 'email')  // Error

2.2 条件类型

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

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

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

// infer关键字
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never
type UnpackPromise<T> = T extends Promise<infer U> ? U : T

type A = ReturnType<() => string>  // string
type B = UnpackPromise<Promise<number>>  // number
type C = UnpackPromise<string>  // string

2.3 映射类型

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

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

// 选择部分属性
type Pick<T, K extends keyof T> = {
  [P in K]: T[P]
}

// 排除部分属性
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>

// 使用
interface User {
  id: number
  name: string
  email: string
  password: string
}

type UserPreview = Pick<User, 'id' | 'name'>
type UserWithoutPassword = Omit<User, 'password'>
type PartialUser = Partial<User>
type ReadonlyUser = Readonly<User>


三、模板字面量类型

// 基本用法
type Color = 'red' | 'blue' | 'green'
type Size = 'sm' | 'md' | 'lg'

type ColorSize = `${Color}-${Size}`
// "red-sm" | "red-md" | "red-lg" | "blue-sm" | ...

// 事件名
type EventName<T extends string> = `on${Capitalize<T>}`
type ClickEvent = EventName<'click'>  // "onClick"
type MouseEvent = EventName<'mouse'>  // "onMouse"

// CSS属性
type CSSProperty = `margin-${'top' | 'right' | 'bottom' | 'left'}`
// "margin-top" | "margin-right" | "margin-bottom" | "margin-left"


四、实用工具类型

// Required:所有属性变为必需
type Required<T> = {
  [P in keyof T]-?: T[P]
}

// Record:构造键值对类型
type Record<K extends keyof any, T> = {
  [P in K]: T
}

// Exclude:从联合类型中排除
type Exclude<T, U> = T extends U ? never : T

// Extract:从联合类型中提取
type Extract<T, U> = T extends U ? T : never

// NonNullable:排除null和undefined
type NonNullable<T> = T extends null | undefined ? never : T

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

// ConstructorParameters:获取构造函数参数类型
type ConstructorParameters<T extends abstract new (...args: any) => any> = 
  T extends abstract new (...args: infer P) => any ? P : never

// InstanceType:获取实例类型
type InstanceType<T extends abstract new (...args: any) => any> = 
  T extends abstract new (...args: any) => infer R ? R : any


五、类型体操练习

5.1 实现DeepReadonly

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

interface Config {
  db: {
    host: string
    port: number
  }
  cache: {
    ttl: number
  }
}

type ReadonlyConfig = DeepReadonly<Config>
// {
//   readonly db: {
//     readonly host: string
//     readonly port: number
//   }
//   readonly cache: {
//     readonly ttl: number
//   }
// }

5.2 实现PartialByKeys

type PartialByKeys<T, K extends keyof T> = 
  Omit<T, K> & Partial<Pick<T, K>>

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

type CreateUser = PartialByKeys<User, 'id'>
// { id?: number; name: string; email: string }

5.3 实现RequiredByKeys

type RequiredByKeys<T, K extends keyof T> = 
  Omit<T, K> & Required<Pick<T, K>>

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

type RequiredUser = RequiredByKeys<User, 'id' | 'name'>
// { id: number; name: number; email?: string }


六、React + TypeScript 高级

6.1 泛型组件

// 泛型列表组件
interface ListProps<T> {
  items: T[]
  renderItem: (item: T) => React.ReactNode
  keyExtractor: (item: T) => string
}

function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
  return (
    <ul>
      {items.map(item => (
        <li key={keyExtractor(item)}>{renderItem(item)}</li>
      ))}
    </ul>
  )
}

// 使用
<List
  items={users}
  renderItem={user => <span>{user.name}</span>}
  keyExtractor={user => String(user.id)}
/>

6.2 泛型Hook

// 泛型useFetch
function useFetch<T>(url: string) {
  const [data, setData] = useState<T | null>(null)
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState<string | null>(null)
  
  useEffect(() => {
    fetch(url)
      .then(res => res.json())
      .then(setData)
      .catch(err => setError(err.message))
      .finally(() => setLoading(false))
  }, [url])
  
  return { data, loading, error }
}

// 使用
const { data: users } = useFetch<User[]>('/api/users')


学习建议

  1. 先掌握基础类型,再学高级类型
  2. 理解泛型的本质,类型参数化
  3. 练习类型体操,提升类型编程能力
  4. 在实际项目中应用,特别是库开发
  5. 不要过度类型化,简单的地方让TS推断
返回首页