TypeScript深度强化第二天

60 阅读20分钟

TypeScript 深度强化第二天:从类型创建类型的高级技术

掌握 TypeScript 类型系统的核心能力:泛型、类型运算符与高级类型转换

🎯 学习目标

通过第二天的学习,你将:

  • 深度理解泛型的设计思想和应用场景
  • 掌握 keyof、typeof 等核心类型运算符
  • 学会使用条件类型进行类型推导
  • 熟练运用映射类型和模板字面量类型
  • 构建可复用的高级类型工具

第 1 章:泛型编程的艺术

泛型的本质与价值

什么是泛型?

泛型就像是一个"万能盒子",你不知道里面装的是什么,但是你知道无论装什么,盒子的使用方法都是一样的。比如说,你有一个函数要处理数据,但你不知道这个数据是字符串、数字还是对象,泛型就能帮你写一个通用的处理函数。

为什么需要泛型?

想象一下,如果没有泛型,你要写一个返回输入值的函数:

  • 处理字符串:function returnString(input: string): string
  • 处理数字:function returnNumber(input: number): number
  • 处理布尔值:function returnBoolean(input: boolean): boolean

这样要写无数个函数!泛型就是为了解决这个重复代码的问题。

核心概念深度理解:

  • 类型参数化:就像函数可以接收参数一样,泛型让类型也能接收"参数"
  • 类型安全:虽然是通用的,但 TypeScript 仍然知道具体的类型,不会出错
  • 代码复用:一套代码,多种类型都能用
  • 延迟类型确定:在定义时不确定类型,在使用时才确定

泛型的工作原理:

  1. 定义时:用<T>表示"这里有个类型,但现在不知道是什么"
  2. 使用时:TypeScript 会根据你传入的值自动推断类型
  3. 编译时:TypeScript 会检查类型是否匹配,确保安全
// 最基础的泛型函数 - 就像一个回音壁,你说什么它返回什么
function identity<T>(arg: T): T {
  return arg
}

泛型的类型推断:

TypeScript 非常聪明,大多数情况下能自动推断出泛型的具体类型,你不需要手动指定:

// TypeScript会自动推断类型,非常智能
let result1 = identity('hello world') // TypeScript知道这是string
let result2 = identity(42) // TypeScript知道这是number
let result3 = identity(true) // TypeScript知道这是boolean

// 你也可以明确指定类型,有时候TypeScript推断不出来
let result4 = identity<string>('explicit type')
let result5 = identity<number[]>([1, 2, 3, 4])

泛型的类型传递:

泛型最强大的地方是类型信息会在整个调用链中传递,确保类型安全:

// 泛型的强大之处:类型信息会传递
function getArrayLength<T>(arr: T[]): number {
  return arr.length // TypeScript知道arr是数组,所以有length属性
}

let stringArrayLength = getArrayLength(['a', 'b', 'c']) // 返回3
let numberArrayLength = getArrayLength([1, 2, 3, 4, 5]) // 返回5

泛型与 keyof 的结合:

这是一个更高级的例子,展示了泛型如何与其他 TypeScript 特性结合使用:

// 更复杂的例子:处理对象
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key] // 这里TypeScript知道返回值的确切类型
}

const person = {
  name: 'John',
  age: 30,
  isStudent: false,
}

const personName = getProperty(person, 'name') // TypeScript知道这是string
const personAge = getProperty(person, 'age') // TypeScript知道这是number
// const invalid = getProperty(person, "height"); // 编译错误!person没有height属性

泛型约束的深度应用

什么是泛型约束?

泛型约束就像是给"万能盒子"加了一些限制条件。比如说,这个盒子只能装"有轮子的东西",那汽车、自行车可以装,但是石头就不行。

为什么需要约束?

有时候你的泛型函数需要使用某些特定的属性或方法,但是如果不加约束,TypeScript 不知道这个类型有没有这些属性,就会报错。

约束的几种常见形式:

  1. 基础约束:要求类型必须有某些属性
  2. keyof 约束:要求类型必须是某个对象的键名
  3. 条件约束:根据条件动态约束
  4. 多重约束:同时满足多个条件

1. 基础约束:要求类型具有特定属性

最简单的约束是要求泛型类型必须具有某些属性:

// 1. 基础约束:要求类型必须有length属性
interface HasLength {
  length: number
}

// 这个函数只接受有length属性的类型
function logLength<T extends HasLength>(arg: T): T {
  console.log(`Length is: ${arg.length}`) // 现在可以安全使用length了
  return arg
}

logLength('hello') // ✅ 字符串有length
logLength([1, 2, 3]) // ✅ 数组有length
logLength({ length: 10, value: 'test' }) // ✅ 对象有length
// logLength(123); // ❌ 数字没有length,编译错误

2. keyof 约束:确保属性名存在

这种约束确保我们只能访问对象中真实存在的属性:

// 2. keyof约束:确保键名存在于对象中
function getObjectProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key]
}

const user = {
  username: 'alice',
  age: 25,
  email: 'alice@example.com',
  isActive: true,
}

const username = getObjectProperty(user, 'username') // string类型
const age = getObjectProperty(user, 'age') // number类型
// const invalid = getObjectProperty(user, "height"); // 编译错误!

3. 多重约束:同时满足多个条件

有时候我们需要类型同时满足多个条件,可以使用交叉类型(&):

// 3. 多重约束:同时满足多个条件
interface Serializable {
  serialize(): string
}

interface Timestamped {
  timestamp: Date
}

// 要求类型同时实现两个接口
function processData<T extends Serializable & Timestamped>(data: T): string {
  const serialized = data.serialize() // 可以调用serialize方法
  const time = data.timestamp.toISOString() // 可以访问timestamp属性
  return `${serialized} at ${time}`
}

4. 条件约束:根据类型特征进行约束

使用条件类型可以创建更复杂的约束逻辑:

// 4. 条件约束:根据类型特征进行约束
type ArrayElement<T> = T extends (infer U)[] ? U : never
type FunctionReturn<T> = T extends (...args: any[]) => infer R ? R : never

type StringArrayElement = ArrayElement<string[]> // string
type NumberArrayElement = ArrayElement<number[]> // number
type FuncReturnType = FunctionReturn<() => boolean> // boolean

5. 实际应用:类型安全的事件系统

让我们看一个完整的实际应用,展示泛型约束的强大威力:

// 5. 实际应用:创建类型安全的事件系统
interface EventMap {
  click: { x: number; y: number }
  keypress: { key: string; ctrl: boolean }
  resize: { width: number; height: number }
}

class EventEmitter<T extends Record<string, any>> {
  private listeners: { [K in keyof T]?: Array<(data: T[K]) => void> } = {}

  on<K extends keyof T>(event: K, callback: (data: T[K]) => void): void {
    if (!this.listeners[event]) {
      this.listeners[event] = []
    }
    this.listeners[event]!.push(callback)
  }

  emit<K extends keyof T>(event: K, data: T[K]): void {
    const callbacks = this.listeners[event]
    if (callbacks) {
      callbacks.forEach(callback => callback(data))
    }
  }
}

事件系统的使用示例:

这个事件系统确保了类型安全,你不能传入错误的事件数据:

const emitter = new EventEmitter<EventMap>()

// 类型安全的事件监听
emitter.on('click', data => {
  console.log(`Clicked at ${data.x}, ${data.y}`) // data自动推断为{x: number, y: number}
})

emitter.on('keypress', data => {
  console.log(`Key pressed: ${data.key}`) // data自动推断为{key: string, ctrl: boolean}
})

// 类型安全的事件触发
emitter.emit('click', { x: 100, y: 200 }) // ✅ 正确
emitter.emit('keypress', { key: 'Enter', ctrl: false }) // ✅ 正确
// emitter.emit("click", { key: "Enter" }); // ❌ 编译错误,类型不匹配

泛型类与泛型接口的实际应用

什么是泛型类?简单来说:

泛型类就像是一个"模板工厂",你可以用同一个模板生产不同类型的产品。比如你有一个数据存储的类,可以存储字符串、数字、对象等任何类型的数据。

泛型类的核心特点:

  • 类的属性和方法可以使用类型参数
  • 静态成员不能使用类的类型参数(这是重要限制!)
  • 可以继承泛型类,也可以实现泛型接口
  • 实例化时需要指定具体类型

泛型接口的作用:

  • 定义通用的契约结构
  • 可以描述函数类型、对象类型、索引类型
  • 支持多个类型参数
  • 可以有默认类型参数

泛型数据容器类的实现:

让我们先实现一个通用的数据容器类,它可以存储任何类型的数据:

// 泛型数据容器类 - 像一个智能仓库
class DataContainer<T> {
  private items: T[] = []

  // 添加数据
  add(item: T): void {
    this.items.push(item)
  }

  // 获取数据
  get(index: number): T | undefined {
    return this.items[index]
  }

  // 查找数据 - 这里展示了泛型约束的使用
  find<K extends keyof T>(property: K, value: T[K]): T[] {
    return this.items.filter(item => item[property] === value)
  }

  // 获取所有数据
  getAll(): readonly T[] {
    return [...this.items] // 返回副本,保护内部数据
  }

  // 获取数量
  get count(): number {
    return this.items.length
  }

  // 清空数据
  clear(): void {
    this.items = []
  }
}

使用数据容器的实际例子:

现在让我们看看如何在实际项目中使用这个数据容器:

// 使用示例1:存储用户信息
interface User {
  id: number
  name: string
  email: string
  isActive: boolean
}

const userContainer = new DataContainer<User>()
userContainer.add({ id: 1, name: 'Alice', email: 'alice@example.com', isActive: true })
userContainer.add({ id: 2, name: 'Bob', email: 'bob@example.com', isActive: false })

// 类型安全的查找
const activeUsers = userContainer.find('isActive', true) // 返回User[]
const userById = userContainer.find('id', 1) // 返回User[]

// 使用示例2:存储产品信息
interface Product {
  sku: string
  name: string
  price: number
  category: string
}

const productContainer = new DataContainer<Product>()
productContainer.add({ sku: 'P001', name: 'Laptop', price: 999, category: 'Electronics' })

泛型接口的设计:

接下来我们定义一些通用的接口,用于 API 响应格式:

// 泛型接口:API响应格式
interface ApiResponse<TData, TError = string> {
  success: boolean
  data?: TData
  error?: TError
  message: string
  timestamp: number
}

// 分页响应接口
interface PaginatedResponse<T> extends ApiResponse<T[]> {
  pagination: {
    page: number
    limit: number
    total: number
    totalPages: number
  }
}

// 实际使用
type UserListResponse = PaginatedResponse<User>
type ProductResponse = ApiResponse<Product>
type ErrorResponse = ApiResponse<null, { code: number; details: string }>

泛型工厂函数:

工厂函数可以帮助我们快速创建符合规范的响应对象:

// 泛型工厂函数
function createSuccessResponse<T>(data: T): ApiResponse<T> {
  return {
    success: true,
    data,
    message: 'Operation successful',
    timestamp: Date.now(),
  }
}

function createErrorResponse<T = null>(error: string): ApiResponse<T> {
  return {
    success: false,
    error,
    message: 'Operation failed',
    timestamp: Date.now(),
  }
}

// 使用工厂函数
const userResponse = createSuccessResponse({ id: 1, name: 'Alice', email: 'alice@example.com', isActive: true })
const errorResponse = createErrorResponse('User not found')

高级泛型类:缓存管理器

让我们实现一个更复杂的泛型类,展示多个泛型参数的使用:

// 高级泛型类:缓存管理器
class CacheManager<TKey extends string | number, TValue> {
  private cache = new Map<TKey, { value: TValue; expiry: number }>()
  private defaultTTL: number

  constructor(defaultTTL: number = 300000) {
    // 默认5分钟
    this.defaultTTL = defaultTTL
  }

  set(key: TKey, value: TValue, ttl?: number): void {
    const expiry = Date.now() + (ttl || this.defaultTTL)
    this.cache.set(key, { value, expiry })
  }

  get(key: TKey): TValue | null {
    const item = this.cache.get(key)
    if (!item) return null

    if (Date.now() > item.expiry) {
      this.cache.delete(key)
      return null
    }

    return item.value
  }

  has(key: TKey): boolean {
    return this.get(key) !== null
  }

  delete(key: TKey): boolean {
    return this.cache.delete(key)
  }

  clear(): void {
    this.cache.clear()
  }

  // 清理过期项
  cleanup(): number {
    const now = Date.now()
    let cleaned = 0

    for (const [key, item] of this.cache.entries()) {
      if (now > item.expiry) {
        this.cache.delete(key)
        cleaned++
      }
    }

    return cleaned
  }
}

缓存管理器的使用:

这个缓存管理器可以用于不同类型的数据缓存:

// 使用缓存管理器
const userCache = new CacheManager<number, User>()
const productCache = new CacheManager<string, Product>()

userCache.set(1, { id: 1, name: 'Alice', email: 'alice@example.com', isActive: true })
productCache.set('P001', { sku: 'P001', name: 'Laptop', price: 999, category: 'Electronics' })

const cachedUser = userCache.get(1) // User | null
const cachedProduct = productCache.get('P001') // Product | null

泛型类与泛型接口

泛型不仅可以应用于函数,还可以应用于类和接口,提供强大的类型复用能力。

设计原则:

  • 泛型类的静态成员不能使用类的类型参数
  • 泛型接口可以描述函数类型、索引类型等
  • 泛型约束在类和接口中同样适用
  • 继承关系中的泛型传递规则
// 泛型数据存储类
class DataStorage<DataType> {
  private dataList: DataType[] = []

  add(item: DataType): void {
    this.dataList.push(item)
  }

  get(index: number): DataType | undefined {
    return this.dataList[index]
  }

  find<KeyName extends keyof DataType>(key: KeyName, value: DataType[KeyName]): DataType[] {
    return this.dataList.filter(item => item[key] === value)
  }

  getAll(): readonly DataType[] {
    return [...this.dataList]
  }
}

// 使用示例
interface ProductInfo {
  productId: string
  productName: string
  price: number
  category: string
}

const productStorage = new DataStorage<ProductInfo>()
productStorage.add({
  productId: 'P001',
  productName: 'Smartphone',
  price: 2999,
  category: 'Electronics',
})

const electronics = productStorage.find('category', 'Electronics')

// 泛型接口:API响应类型
interface ApiResponse<DataType> {
  success: boolean
  message: string
  data?: DataType
  errorCode?: number
  timestamp: number
}

interface PaginatedResponse<ItemType> extends ApiResponse<ItemType[]> {
  pagination: {
    currentPage: number
    pageSize: number
    total: number
    totalPages: number
  }
}

// 工厂函数模式
function createApiResponse<T>(data: T): ApiResponse<T> {
  return {
    success: true,
    message: 'Operation successful',
    data,
    timestamp: Date.now(),
  }
}

const userResponse = createApiResponse({ userId: 1, username: 'admin' })

第 2 章:keyof 类型运算符的威力

keyof 运算符基础 - 获取对象所有键名

什么是 keyof?

keyof 就像是一个"钥匙串提取器",给它一个对象类型,它就能把这个对象所有的属性名(键名)提取出来,组成一个联合类型。

想象你有一个房子,keyof 就能告诉你这个房子有哪些房间的钥匙。

为什么 keyof 这么重要?

  1. 类型安全:确保你只能访问对象真实存在的属性
  2. 动态属性访问:可以根据属性名动态获取属性值
  3. 工具类型基础:很多高级类型都是基于 keyof 构建的
  4. 重构友好:当对象结构改变时,相关类型会自动更新

keyof 的工作原理:

interface Person {
  name: string
  age: number
  email: string
}

// keyof Person 等于 "name" | "age" | "email"
type PersonKeys = keyof Person

深入理解 keyof 的特性:

  1. 基础用法:提取对象类型的所有键名
  2. 索引签名处理:对于索引签名,返回索引的类型
  3. 数字键的特殊处理:JavaScript 中数字键会被转换为字符串
  4. Symbol 键的支持:也可以提取 Symbol 类型的键
  5. 联合类型的分布式特性:对联合类型使用 keyof 会有特殊行为

1. 基础 keyof 使用:

让我们从一个简单的用户接口开始,看看 keyof 是如何工作的:

// 1. 基础keyof使用
interface User {
  id: number
  username: string
  email: string
  isActive: boolean
  profile: {
    firstName: string
    lastName: string
  }
}

type UserKeys = keyof User // "id" | "username" | "email" | "isActive" | "profile"

类型安全的属性访问:

有了 keyof,我们可以创建类型安全的属性访问函数:

// 实际应用:类型安全的属性访问函数
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key]
}

const user: User = {
  id: 1,
  username: 'john_doe',
  email: 'john@example.com',
  isActive: true,
  profile: { firstName: 'John', lastName: 'Doe' },
}

const userId = getProperty(user, 'id') // number类型
const username = getProperty(user, 'username') // string类型
const profile = getProperty(user, 'profile') // {firstName: string, lastName: string}类型
// const invalid = getProperty(user, "nonexistent"); // 编译错误!

2. 索引签名的 keyof 行为:

当接口有索引签名时,keyof 的行为会有所不同:

// 2. 索引签名的keyof行为
interface StringDictionary {
  [key: string]: any
}

interface NumberDictionary {
  [key: number]: any
}

type StringDictKeys = keyof StringDictionary // string | number
type NumberDictKeys = keyof NumberDictionary // number

// 为什么StringDictionary的keyof包含number?
// 因为JavaScript中 obj[1] 等价于 obj["1"]

3. 混合索引签名的处理:

当接口既有具体属性又有索引签名时,keyof 会如何处理:

// 3. 混合索引签名
interface MixedInterface {
  name: string
  [key: string]: any
}

type MixedKeys = keyof MixedInterface // string | number
// 这里包含了"name",但由于索引签名的存在,结果是string | number

4. 数字键的特殊处理:

JavaScript 中的数字键会被转换为字符串,keyof 也遵循这个规则:

// 4. 数字键的特殊处理
interface ArrayLike {
  0: string
  1: number
  2: boolean
  length: number
}

type ArrayLikeKeys = keyof ArrayLike // "0" | "1" | "2" | "length"
// 注意:数字键被转换为字符串字面量类型

5. Symbol 键的支持:

keyof 也支持 Symbol 类型的键:

// 5. Symbol键的支持
const symbolKey = Symbol('mySymbol')

interface WithSymbol {
  name: string
  [symbolKey]: number
}

type WithSymbolKeys = keyof WithSymbol // "name" | typeof symbolKey

6. 联合类型与交叉类型的 keyof:

这是一个重要概念,联合类型和交叉类型的 keyof 行为不同:

// 6. 联合类型的keyof - 重要概念!
interface Cat {
  name: string
  meow(): void
}

interface Dog {
  name: string
  bark(): void
}

// 联合类型的keyof只包含共同的键
type AnimalKeys = keyof (Cat | Dog) // "name"
// 因为只有name是Cat和Dog都有的属性

// 7. 交叉类型的keyof
type CatDogKeys = keyof (Cat & Dog) // "name" | "meow" | "bark"
// 交叉类型包含所有属性

8. 实际应用:对象更新函数:

让我们看看如何在实际项目中使用 keyof 创建实用的工具函数:

// 8. 实际应用:创建类型安全的对象更新函数
function updateObject<T>(obj: T, updates: Partial<T>): T {
  return { ...obj, ...updates }
}

// 更高级的版本:只允许更新特定字段
function updateSpecificFields<T, K extends keyof T>(obj: T, fields: K[], updates: Pick<T, K>): T {
  const result = { ...obj }
  fields.forEach(field => {
    if (field in updates) {
      result[field] = updates[field]
    }
  })
  return result
}

// 使用示例
const updatedUser = updateSpecificFields(
  user,
  ['username', 'email'], // 只能更新这些字段
  { username: 'new_username', email: 'new@example.com' },
)

9. keyof 与泛型约束的结合:

这种模式在创建工具函数时非常有用:

// 9. keyof与泛型约束的结合
function createPropertyGetter<T>() {
  return function <K extends keyof T>(obj: T, key: K): T[K] {
    return obj[key]
  }
}

const getUserProperty = createPropertyGetter<User>()
const userEmail = getUserProperty(user, 'email') // string类型

10. 条件类型中的 keyof 应用:

最后,让我们看看 keyof 在条件类型中的高级应用:

// 10. 条件类型中的keyof应用
type FunctionPropertyNames<T> = {
  [K in keyof T]: T[K] extends Function ? K : never
}[keyof T]

type NonFunctionPropertyNames<T> = {
  [K in keyof T]: T[K] extends Function ? never : K
}[keyof T]

interface ExampleObject {
  name: string
  age: number
  greet(): void
  calculate(x: number): number
}

type FunctionProps = FunctionPropertyNames<ExampleObject> // "greet" | "calculate"
type NonFunctionProps = NonFunctionPropertyNames<ExampleObject> // "name" | "age"

高级 keyof 应用技巧

keyof 在实际开发中的常见模式:

  1. 属性选择器模式:创建类型安全的属性选择器
  2. 对象验证模式:验证对象是否包含必需属性
  3. 动态表单模式:根据对象结构动态生成表单
  4. 序列化模式:类型安全的对象序列化/反序列化
// 1. 属性选择器模式
class PropertySelector<T> {
  constructor(private obj: T) {}

  select<K extends keyof T>(...keys: K[]): Pick<T, K> {
    const result = {} as Pick<T, K>
    keys.forEach(key => {
      result[key] = this.obj[key]
    })
    return result
  }

  exclude<K extends keyof T>(...keys: K[]): Omit<T, K> {
    const result = { ...this.obj }
    keys.forEach(key => {
      delete result[key]
    })
    return result
  }
}

// 使用示例
const selector = new PropertySelector(user)
const basicInfo = selector.select('id', 'username') // { id: number; username: string }
const withoutProfile = selector.exclude('profile') // 去掉profile的User类型

// 2. 对象验证模式
function validateRequiredFields<T, K extends keyof T>(obj: Partial<T>, requiredFields: K[]): obj is Pick<T, K> & Partial<T> {
  return requiredFields.every(field => field in obj && obj[field] !== undefined)
}

// 使用示例
const partialUser: Partial<User> = { username: 'test' }
if (validateRequiredFields(partialUser, ['id', 'username'])) {
  // 在这个if块中,TypeScript知道partialUser一定有id和username
  console.log(partialUser.id) // 类型安全
  console.log(partialUser.username) // 类型安全
}

// 3. 动态表单字段生成
type FormField<T, K extends keyof T> = {
  key: K
  label: string
  type: T[K] extends string ? 'text' : T[K] extends number ? 'number' : T[K] extends boolean ? 'checkbox' : 'unknown'
  required: boolean
}

function createFormFields<T>(): <K extends keyof T>(configs: { [P in K]: { label: string; required: boolean } }) => FormField<T, K>[] {
  return function <K extends keyof T>(configs) {
    return (Object.keys(configs) as K[]).map(key => ({
      key,
      label: configs[key].label,
      type: 'text' as any, // 简化示例
      required: configs[key].required,
    }))
  }
}

// 使用示例
const createUserFormFields = createFormFields<User>()
const formFields = createUserFormFields({
  username: { label: '用户名', required: true },
  email: { label: '邮箱', required: true },
  isActive: { label: '是否激活', required: false },
})

// 4. 类型安全的深度路径访问
type DeepKeys<T> = T extends object
  ? {
      [K in keyof T]: K extends string | number ? (T[K] extends object ? `${K}` | `${K}.${DeepKeys<T[K]>}` : `${K}`) : never
    }[keyof T]
  : never

type UserDeepKeys = DeepKeys<User> // "id" | "username" | "email" | "isActive" | "profile" | "profile.firstName" | "profile.lastName"

function getDeepProperty<T, K extends DeepKeys<T>>(obj: T, path: K): any {
  const keys = path.split('.')
  let result: any = obj
  for (const key of keys) {
    result = result[key]
    if (result === undefined) break
  }
  return result
}

// 使用示例
const firstName = getDeepProperty(user, 'profile.firstName') // 类型安全的深度访问
// const invalid = getDeepProperty(user, "profile.age"); // 编译错误!
  • keyof 在数组和元组上的特殊行为
// 基础keyof使用
interface EmployeeInfo {
  employeeId: string
  name: string
  department: string
  salary: number
  hireDate: Date
}

type EmployeeFieldNames = keyof EmployeeInfo
// "employeeId" | "name" | "department" | "salary" | "hireDate"

// 索引签名的keyof
interface DynamicConfig {
  [configName: string]: any
}
type ConfigKeyType = keyof DynamicConfig // string | number

interface ArrayIndexType {
  [index: number]: string
}
type ArrayKeyType = keyof ArrayIndexType // number

// 联合类型的keyof
type TypeA = { a: string; commonField: boolean }
type TypeB = { b: string; commonField: boolean }
type CommonKeys = keyof (TypeA | TypeB) // "commonField"

// 交叉类型的keyof
type AllKeys = keyof (TypeA & TypeB) // "a" | "b" | "commonField"

keyof 在类型安全中的应用

keyof 的主要价值在于提供类型安全的属性访问,防止运行时错误。

应用场景:

  • 动态属性访问的类型检查
  • 对象属性的批量操作
  • 配置对象的类型安全
  • API 字段验证和转换
// 类型安全的属性获取器
function safeGetProperty<ObjectType, KeyName extends keyof ObjectType>(object: ObjectType, keyName: KeyName): ObjectType[KeyName] {
  return object[keyName]
}

// 批量属性选择器
function selectProperties<ObjectType, KeyName extends keyof ObjectType>(object: ObjectType, keyList: KeyName[]): Pick<ObjectType, KeyName> {
  const result = {} as Pick<ObjectType, KeyName>
  keyList.forEach(key => {
    result[key] = object[key]
  })
  return result
}

// 属性验证器
function validateRequiredProperties<ObjectType>(object: Partial<ObjectType>, requiredFields: (keyof ObjectType)[]): object is ObjectType {
  return requiredFields.every(field => object[field] !== undefined)
}

// 使用示例
const productInfo = {
  productId: 'SP001',
  productName: 'Premium Headphones',
  price: 599,
  stock: 50,
  description: 'High-quality audio equipment',
}

const productName = safeGetProperty(productInfo, 'productName') // string
const basicInfo = selectProperties(productInfo, ['productId', 'productName', 'price'])

// 表单数据处理
interface UserForm {
  username: string
  email: string
  password: string
  confirmPassword: string
  agreeToTerms: boolean
}

function handleFormSubmission<FormType>(formData: Partial<FormType>, requiredFields: (keyof FormType)[]): { valid: boolean; error?: string; data?: FormType } {
  if (!validateRequiredProperties(formData, requiredFields)) {
    return { valid: false, error: 'Required fields are incomplete' }
  }

  return { valid: true, data: formData }
}

const formResult = handleFormSubmission({ username: 'test', email: 'test@example.com' }, ['username', 'email', 'password'])

keyof 与映射类型的结合

keyof 与映射类型结合使用,能够创建强大的类型转换工具。

核心技术:

  • 属性遍历与类型映射
  • 条件属性过滤
  • 属性名称转换
  • 值类型变换
// 创建可选版本
type MakeOptional<T> = {
  [K in keyof T]?: T[K]
}

// 创建只读版本
type MakeReadonly<T> = {
  readonly [K in keyof T]: T[K]
}

// 移除只读修饰符
type MakeWritable<T> = {
  -readonly [K in keyof T]: T[K]
}

// 移除可选修饰符
type MakeRequired<T> = {
  [K in keyof T]-?: T[K]
}

// 属性类型转换
type Stringify<T> = {
  [K in keyof T]: string
}

// 条件属性过滤
type FilterFunctionProperties<T> = {
  [K in keyof T]: T[K] extends Function ? never : T[K]
}

// 实际应用示例
interface DatabaseModel {
  readonly id: number
  name: string
  description?: string
  createdAt: Date
  updatedAt: Date
  deleteMethod(): void
  saveMethod(): Promise<void>
}

type CreateInput = MakeOptional<Omit<DatabaseModel, 'id' | 'createdAt' | 'updatedAt' | 'deleteMethod' | 'saveMethod'>>
type UpdateInput = Partial<Pick<DatabaseModel, 'name' | 'description'>>
type QueryResult = MakeReadonly<FilterFunctionProperties<DatabaseModel>>

// 高级属性映射
type PropertyGetters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
}

type PropertySetters<T> = {
  [K in keyof T as `set${Capitalize<string & K>}`]: (value: T[K]) => void
}

type CompleteAccessors<T> = PropertyGetters<T> & PropertySetters<T>

type UserAccessors = CompleteAccessors<Pick<EmployeeInfo, 'name' | 'department'>>
// 生成: getName, setName, getDepartment, setDepartment 方法

第 3 章:typeof 类型运算符 - 从值到类型的桥梁

typeof 运算符的双重身份

什么是 typeof?

typeof 在 TypeScript 中有两个身份,就像一个人有两份工作:

  1. 运行时工作:检查 JavaScript 值的类型(这是 JavaScript 原生功能)
  2. 编译时工作:提取 TypeScript 类型信息(这是 TypeScript 特有功能)

简单来说,typeof 可以把一个具体的值"反向工程"成类型定义。

为什么 typeof 这么有用?

  1. 避免重复定义:有了值,就不需要再单独写类型定义
  2. 保持同步:值改变时,类型自动跟着变
  3. 提取复杂类型:从复杂对象或函数中提取类型
  4. 库类型提取:从第三方库的值中提取类型

typeof 的工作原理:

  • 在类型位置使用时,typeof 会提取值的类型
  • 在表达式位置使用时,typeof 执行运行时类型检查
  • 只能用于标识符或其属性访问
  • 与其他类型运算符组合使用能力强大

从配置对象提取类型:

这是 typeof 最常见的用法,从已有的配置对象中提取类型定义:

// 基础typeof使用
const configObject = {
  database: {
    host: 'localhost',
    port: 5432,
    username: 'admin',
    password: 'secret',
    databaseName: 'myapp',
  },
  cache: {
    enabled: true,
    ttl: 3600,
    maxEntries: 1000,
  },
  logging: {
    level: 'info' as const,
    filePath: '/var/log/app.log',
    maxSize: '10MB',
  },
} as const

type ConfigType = typeof configObject
type DatabaseConfig = typeof configObject.database
type LogLevel = typeof configObject.logging.level // "info"

从函数提取类型信息:

typeof 可以提取函数的完整类型信息,包括参数和返回值:

// 函数类型提取
function calculateTotal(itemList: { name: string; price: number; quantity: number }[], discountRate: number = 0): { total: number; discountAmount: number; finalPrice: number } {
  const total = itemList.reduce((sum, item) => sum + item.price * item.quantity, 0)
  const discountAmount = total * discountRate
  return {
    total,
    discountAmount,
    finalPrice: total - discountAmount,
  }
}

type CalculateFunctionType = typeof calculateTotal
type CalculateParameterTypes = Parameters<typeof calculateTotal>
type CalculateReturnType = ReturnType<typeof calculateTotal>

从类提取类型信息:

typeof 在处理类时有特殊的行为,可以提取构造函数类型和实例类型:

// 类类型提取
class UserManager {
  private userList: Map<string, any> = new Map()

  addUser(user: { id: string; name: string }): void {
    this.userList.set(user.id, user)
  }

  getUser(id: string): any {
    return this.userList.get(id)
  }

  static createInstance(): UserManager {
    return new UserManager()
  }
}

type UserManagerType = typeof UserManager // 构造函数类型
type UserManagerInstanceType = InstanceType<typeof UserManager>

typeof 与对象类型提取

typeof 最常见的用途是从复杂对象中提取类型定义,避免重复定义类型。

最佳实践:

  • 使用 as const 获得精确的字面量类型
  • 结合 keyof 进行属性枚举
  • 通过索引访问获取嵌套类型
  • 构建类型安全的配置系统
// 主题配置系统
const themeConfig = {
  colors: {
    primary: '#3498db',
    secondary: '#2ecc71',
    warning: '#f39c12',
    danger: '#e74c3c',
    text: {
      primary: '#2c3e50',
      secondary: '#7f8c8d',
      disabled: '#bdc3c7',
    },
  },
  fonts: {
    sizes: {
      small: '12px',
      medium: '14px',
      large: '16px',
      xlarge: '20px',
    },
    weights: {
      light: 300,
      normal: 400,
      bold: 600,
      extraBold: 800,
    },
  },
  spacing: {
    xs: '4px',
    sm: '8px',
    md: '16px',
    lg: '24px',
    xl: '32px',
  },
  breakpoints: {
    mobile: '768px',
    tablet: '1024px',
    desktop: '1200px',
  },
} as const

// 类型提取
type ThemeType = typeof themeConfig
type ColorConfig = typeof themeConfig.colors
type FontSizes = typeof themeConfig.fonts.sizes
type SpacingValues = (typeof themeConfig.spacing)[keyof typeof themeConfig.spacing]

// 主题工具函数
function getThemeValue<Path extends keyof ThemeType>(path: Path): ThemeType[Path] {
  return themeConfig[path]
}

function getNestedValue<Category extends keyof ThemeType, Key extends keyof ThemeType[Category]>(category: Category, key: Key): ThemeType[Category][Key] {
  return themeConfig[category][key]
}

// 使用示例
const primaryColor = getNestedValue('colors', 'primary') // "#3498db"
const mediumFonts = getNestedValue('fonts', 'sizes') // { small: "12px", ... }

// API路由配置
const apiRoutes = {
  users: {
    list: '/api/users',
    detail: '/api/users/:id',
    create: '/api/users',
    update: '/api/users/:id',
    delete: '/api/users/:id',
  },
  products: {
    list: '/api/products',
    detail: '/api/products/:id',
    search: '/api/products/search',
  },
  orders: {
    list: '/api/orders',
    create: '/api/orders',
    updateStatus: '/api/orders/:id/status',
  },
} as const

type ApiRoutesType = typeof apiRoutes
type UserRoutes = typeof apiRoutes.users
type AllRouteValues = ApiRoutesType[keyof ApiRoutesType][keyof ApiRoutesType[keyof ApiRoutesType]]

// 路由构建器
function buildURL<Module extends keyof ApiRoutesType, Endpoint extends keyof ApiRoutesType[Module]>(module: Module, endpoint: Endpoint, params?: Record<string, string | number>): string {
  let url = apiRoutes[module][endpoint] as string

  if (params) {
    Object.entries(params).forEach(([key, value]) => {
      url = url.replace(`:${key}`, String(value))
    })
  }

  return url
}

const userDetailURL = buildURL('users', 'detail', { id: '123' }) // "/api/users/123"

typeof 与枚举类型

typeof 在处理枚举类型时有特殊的行为和用途。

关键概念:

  • 枚举的值类型与键类型的区别
  • 数字枚举与字符串枚举的 typeof 行为
  • 枚举的反向映射特性
  • const 断言的影响
// 字符串枚举
enum UserRole {
  Admin = 'ADMIN',
  Editor = 'EDITOR',
  Viewer = 'VIEWER',
  Guest = 'GUEST',
}

type RoleKeys = keyof typeof UserRole // "Admin" | "Editor" | "Viewer" | "Guest"
type RoleValues = (typeof UserRole)[keyof typeof UserRole] // "ADMIN" | "EDITOR" | "VIEWER" | "GUEST"

// 数字枚举
enum OrderStatus {
  Pending, // 0
  Paid, // 1
  Shipped, // 2
  Completed, // 3
  Cancelled, // 4
}

type StatusKeys = keyof typeof OrderStatus // "Pending" | "Paid" | "Shipped" | "Completed" | "Cancelled"
type StatusValues = (typeof OrderStatus)[keyof typeof OrderStatus] // 0 | 1 | 2 | 3 | 4

// 枚举工具函数
function isValidRole(value: string): value is UserRole {
  return Object.values(UserRole).includes(value as UserRole)
}

function getRoleDisplayName(role: UserRole): string {
  const displayNameMap: Record<UserRole, string> = {
    [UserRole.Admin]: 'System Administrator',
    [UserRole.Editor]: 'Content Editor',
    [UserRole.Viewer]: 'Read-only User',
    [UserRole.Guest]: 'Guest User',
  }
  return displayNameMap[role]
}

// 对象枚举替代方案
const permissionLevels = {
  read: 'READ',
  write: 'WRITE',
  delete: 'DELETE',
  admin: 'ADMIN',
} as const

type PermissionKeys = keyof typeof permissionLevels
type PermissionValues = (typeof permissionLevels)[keyof typeof permissionLevels]

// 权限检查函数
function checkPermission<T extends PermissionValues>(userPermissions: T[], requiredPermission: T): boolean {
  return userPermissions.includes(requiredPermission)
}

const userPermissionList: PermissionValues[] = ['READ', 'WRITE']
const canDelete = checkPermission(userPermissionList, permissionLevels.delete) // false

第 4 章:索引访问类型的精确控制

索引访问类型基础

索引访问类型允许我们查询另一个类型上的特定属性类型,提供了精确的类型提取能力。

核心概念:

  • 使用方括号语法访问类型的属性
  • 支持联合类型作为索引
  • 可以与其他类型运算符组合
  • 在数组和元组上的特殊应用

设计原理:

  • 类型级别的属性访问模拟
  • 联合类型的分发特性
  • 嵌套类型的递归访问
  • 条件类型的集成应用
// 基础索引访问
interface ECommerceSystem {
  user: {
    profile: {
      username: string
      email: string
      avatar: string
    }
    preferences: {
      language: 'zh' | 'en'
      theme: 'light' | 'dark'
      notifications: boolean
    }
    cart: {
      items: Array<{
        productId: string
        quantity: number
        price: number
      }>
      totalAmount: number
    }
  }
  product: {
    basicInfo: {
      productId: string
      name: string
      description: string
      category: string
    }
    pricing: {
      originalPrice: number
      currentPrice: number
      discount: number
    }
    inventory: {
      totalStock: number
      availableStock: number
      reservedStock: number
    }
  }
}

// 精确类型提取
type UserInfo = ECommerceSystem['user']
type UserProfile = ECommerceSystem['user']['profile']
type UsernameType = ECommerceSystem['user']['profile']['username'] // string
type LanguageOptions = ECommerceSystem['user']['preferences']['language'] // "zh" | "en"

// 联合索引访问
type UserRelatedTypes = ECommerceSystem['user']['profile' | 'preferences']
type ProductPricingRelated = ECommerceSystem['product']['pricing' | 'inventory']

// 动态索引访问
type GetNestedType<ObjectType, Path1 extends keyof ObjectType, Path2 extends keyof ObjectType[Path1]> = ObjectType[Path1][Path2]

type CartType = GetNestedType<ECommerceSystem, 'user', 'cart'>

// 数组元素类型提取
type CartItem = ECommerceSystem['user']['cart']['items'][number]
type ProductIdType = CartItem['productId'] // string

数组与元组的索引访问

索引访问类型在处理数组和元组时展现出强大的类型提取能力。

核心技术:

  • number 索引获取数组元素类型
  • 具体数字索引获取元组特定位置类型
  • length 属性获取元组长度
  • 数组方法的返回类型提取
// 数组类型操作
const userList = [
  { ID: 1, name: 'John', department: 'Engineering', salary: 8000 },
  { ID: 2, name: 'Jane', department: 'Sales', salary: 6000 },
  { ID: 3, name: 'Bob', department: 'HR', salary: 7000 },
]

type UserArrayType = typeof userList
type SingleUserType = UserArrayType[number] // 数组元素类型
type UserIdType = SingleUserType['ID'] // number
type UserNameType = SingleUserType['name'] // string

// 元组类型操作
type CoordinateTuple = [x: number, y: number, z?: number]
type XCoordinateType = CoordinateTuple[0] // number
type YCoordinateType = CoordinateTuple[1] // number
type ZCoordinateType = CoordinateTuple[2] // number | undefined
type CoordinateLength = CoordinateTuple['length'] // 2 | 3

// 复杂数组类型提取
interface OrderData {
  orderList: Array<{
    orderId: string
    customerInfo: {
      customerId: string
      customerName: string
      contactInfo: string
    }
    items: Array<{
      productCode: string
      productName: string
      unitPrice: number
      quantity: number
      subtotal: number
    }>
    status: 'pending' | 'paid' | 'shipped' | 'completed' | 'cancelled'
    createdAt: Date
  }>
}

type OrderType = OrderData['orderList'][number]
type CustomerInfoType = OrderType['customerInfo']
type OrderItemType = OrderType['items'][number]
type OrderStatusType = OrderType['status']

// 数组操作工具类型
type ArrayElement<T> = T extends readonly (infer U)[] ? U : never
type ArrayFirst<T extends readonly unknown[]> = T extends readonly [infer F, ...unknown[]] ? F : never
type ArrayLast<T extends readonly unknown[]> = T extends readonly [...unknown[], infer L] ? L : never
type ArrayTail<T extends readonly unknown[]> = T extends readonly [unknown, ...infer R] ? R : never

// 使用示例
type StatusList = ['pending', 'processing', 'completed']
type FirstStatus = ArrayFirst<StatusList> // "pending"
type LastStatus = ArrayLast<StatusList> // "completed"
type RestStatuses = ArrayTail<StatusList> // ["processing", "completed"]

// 深度索引访问
type DeepGet<T, K extends string> = K extends `${infer K1}.${infer K2}` ? (K1 extends keyof T ? DeepGet<T[K1], K2> : never) : K extends keyof T ? T[K] : never

type DeepTest = DeepGet<ECommerceSystem, 'user.profile.username'> // string

条件索引访问

结合条件类型,索引访问可以实现更加智能的类型提取和转换。

高级技术:

  • 条件类型中的索引访问
  • 分布式条件类型的索引应用
  • 递归索引访问
  • 类型守卫与索引访问的结合
// 条件索引访问
type GetFunctionProperties<T> = {
  [K in keyof T]: T[K] extends Function ? K : never
}[keyof T]

type GetNonFunctionProperties<T> = {
  [K in keyof T]: T[K] extends Function ? never : K
}[keyof T]

// 示例类型
interface MixedObject {
  name: string
  age: number
  getInfo(): string
  setAge(age: number): void
  isAdult: boolean
  calculate(): number
}

type FunctionPropertyNames = GetFunctionProperties<MixedObject> // "getInfo" | "setAge" | "calculate"
type DataPropertyNames = GetNonFunctionProperties<MixedObject> // "name" | "age" | "isAdult"

// 按类型过滤属性
type FilterByType<T, U> = {
  [K in keyof T]: T[K] extends U ? K : never
}[keyof T]

type StringProperties = FilterByType<MixedObject, string> // "name"
type NumberProperties = FilterByType<MixedObject, number> // "age"
type BooleanProperties = FilterByType<MixedObject, boolean> // "isAdult"

// 递归属性访问
type FlattenObject<T, Prefix extends string = ''> = {
  [K in keyof T as T[K] extends object ? (T[K] extends Function ? `${Prefix}${string & K}` : never) : `${Prefix}${string & K}`]: T[K] extends object
    ? T[K] extends Function
      ? T[K]
      : FlattenObject<T[K], `${Prefix}${string & K}.`>
    : T[K]
}[keyof T]

// 智能属性选择器
type SmartSelect<T, K extends string> = K extends keyof T ? T[K] : K extends `${infer K1}.${infer K2}` ? (K1 extends keyof T ? SmartSelect<T[K1], K2> : never) : never

// 使用示例
interface ComplexStructure {
  basic: {
    id: number
    name: string
  }
  details: {
    description: string
    tags: string[]
    metadata: {
      creator: string
      createdAt: Date
    }
  }
}

type IdType = SmartSelect<ComplexStructure, 'basic.id'> // number
type CreatorType = SmartSelect<ComplexStructure, 'details.metadata.creator'> // string

// 类型安全的属性访问函数
function safeAccess<T, K extends string>(object: T, path: K): SmartSelect<T, K> {
  const pathArray = path.split('.')
  let currentValue: any = object

  for (const key of pathArray) {
    if (currentValue && typeof currentValue === 'object' && key in currentValue) {
      currentValue = currentValue[key]
    } else {
      return undefined as any
    }
  }

  return currentValue
}

const complexObject: ComplexStructure = {
  basic: { id: 1, name: 'Test' },
  details: {
    description: 'Description',
    tags: ['tag1'],
    metadata: { creator: 'admin', createdAt: new Date() },
  },
}

const idValue = safeAccess(complexObject, 'basic.id') // number类型
const creator = safeAccess(complexObject, 'details.metadata.creator') // string类型

第 5 章:条件类型的逻辑推理

条件类型基础语法

条件类型是 TypeScript 类型系统中的逻辑分支,类似于 JavaScript 中的三元运算符,但作用于类型层面。

核心语法:

T extends U ? X : Y

理解要点:

  • extends 关键字表示"可分配给"的关系
  • 条件类型支持嵌套使用
  • 分布式条件类型的特殊行为
  • infer 关键字的类型推断能力

基础条件类型示例:

让我们从最简单的条件类型开始,理解它的基本语法:

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

检测函数类型:

条件类型可以用来检测某个类型是否为函数:

// 函数类型检测
type IsFunction<T> = T extends (...args: any[]) => any ? true : false
type FunctionTest1 = IsFunction<() => void> // true
type FunctionTest2 = IsFunction<string> // false

检测数组类型:

同样的,我们可以检测某个类型是否为数组:

// 数组类型检测
type IsArray<T> = T extends any[] ? true : false
type ArrayTest1 = IsArray<string[]> // true
type ArrayTest2 = IsArray<string> // false

infer 关键字的类型推断

infer 关键字允许我们在条件类型中推断并捕获类型信息。

提取数组元素类型:

infer 最常见的用法是提取数组的元素类型:

// 提取数组元素类型
type ElementType<T> = T extends (infer U)[] ? U : never
type StringElement = ElementType<string[]> // string

提取函数返回类型:

infer 可以推断并提取函数的返回类型:

// 提取函数返回类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never
type FunctionReturn = ReturnType<() => string> // string

提取函数参数类型:

同样,infer 也能提取函数的参数类型:

// 提取函数参数类型
type ParameterTypes<T> = T extends (...args: infer P) => any ? P : never
type FunctionParams = ParameterTypes<(a: string, b: number) => void> // [string, number]

提取 Promise 内部类型:

infer 在处理异步类型时也很有用:

// 提取Promise内部类型
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T
type PromiseContent = UnwrapPromise<Promise<string>> // string

第 6 章:映射类型的转换艺术

映射类型基础

映射类型允许基于现有类型创建新类型,通过遍历类型的键来构建新的类型结构。

基础映射语法:

映射类型的基本语法是遍历类型的每个属性:

// 基础映射语法
type MappingExample<T> = {
  [K in keyof T]: T[K]
}

可选化所有属性:

我们可以将对象的所有属性变为可选:

// 可选化所有属性
type Optional<T> = {
  [K in keyof T]?: T[K]
}

只读化所有属性:

同样,可以将所有属性变为只读:

// 只读化所有属性
type Readonly<T> = {
  readonly [K in keyof T]: T[K]
}

类型转换:

映射类型还可以转换属性的类型:

// 类型转换
type Stringify<T> = {
  [K in keyof T]: string
}

高级映射技术

条件映射:

映射类型可以结合条件类型,根据属性类型进行不同的处理:

// 条件映射
type NonFunctionProperties<T> = {
  [K in keyof T]: T[K] extends Function ? never : T[K]
}

键名重映射:

TypeScript 4.1+ 支持键名重映射,可以改变属性的名称:

// 键名重映射
type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
}

过滤映射:

结合条件类型,我们可以过滤出特定类型的属性:

// 过滤映射
type FilterKeys<T, U> = {
  [K in keyof T as T[K] extends U ? K : never]: T[K]
}

第 7 章:模板字面量类型

模板字面量基础

模板字面量类型允许通过模板字符串语法构建字符串类型。

基础模板字面量:

模板字面量类型使用反引号语法,可以动态构建字符串类型:

// 基础模板字面量
type Greeting<Name extends string> = `Hello ${Name}`
type GreetingJohn = Greeting<'John'> // "Hello John"

组合模板字面量:

可以组合多个类型参数来构建复杂的字符串类型:

// 组合模板字面量
type EventName<Action extends string, Target extends string> = `${Action}${Capitalize<Target>}`
type ClickButton = EventName<'click', 'button'> // "clickButton"

URL 构建:

模板字面量类型在构建 URL 路径时特别有用:

// URL构建
type ApiPath<Version extends string, Resource extends string> = `/api/${Version}/${Resource}`
type UserApi = ApiPath<'v1', 'users'> // "/api/v1/users"

模板字面量的高级应用

路径参数解析:

模板字面量类型可以解析 URL 路径中的参数:

// 路径解析
type ExtractPathParams<Path extends string> = Path extends `${string}:${infer Param}/${infer Rest}`
  ? Param | ExtractPathParams<Rest>
  : Path extends `${string}:${infer Param}`
  ? Param
  : never

type PathParams = ExtractPathParams<'/users/:id/posts/:postId'> // "id" | "postId"

CSS 属性生成:

在构建 CSS 相关的类型时,模板字面量类型也很有用:

// CSS属性生成
type CSSProperties<Prefix extends string> = `${Prefix}-color` | `${Prefix}-size` | `${Prefix}-style`
type BorderProperties = CSSProperties<'border'> // "border-color" | "border-size" | "border-style"

第 8 章:实战项目:构建类型安全的状态管理器

项目需求分析

构建一个类型安全的状态管理器,支持:

  • 强类型的状态定义
  • 类型安全的状态更新
  • 自动的动作类型推导
  • 中间件支持
// 状态管理器核心类型
interface StateManager<StateType> {
  getState(): StateType
  updateState<KeyName extends keyof StateType>(key: KeyName, value: StateType[KeyName]): void
  subscribe(listener: (state: StateType) => void): () => void
}

// 动作类型定义
type Action<Type extends string, Payload = void> = Payload extends void ? { type: Type } : { type: Type; payload: Payload }

// 动作创建器
type ActionCreator<ActionType> = ActionType extends Action<infer T, infer P> ? (P extends void ? () => ActionType : (payload: P) => ActionType) : never

// 实现示例
interface UserState {
  userInfo: { name: string; email: string } | null
  loading: boolean
  errorMessage: string | null
}

type UserAction = Action<'setUser', { name: string; email: string }> | Action<'setLoading', boolean> | Action<'setError', string> | Action<'clearUser'>

// 状态管理器实现
class SimpleStateManager<StateType> implements StateManager<StateType> {
  private state: StateType
  private listeners: ((state: StateType) => void)[] = []

  constructor(initialState: StateType) {
    this.state = initialState
  }

  getState(): StateType {
    return this.state
  }

  updateState<KeyName extends keyof StateType>(key: KeyName, value: StateType[KeyName]): void {
    this.state = { ...this.state, [key]: value }
    this.notifyListeners()
  }

  subscribe(listener: (state: StateType) => void): () => void {
    this.listeners.push(listener)
    return () => {
      const index = this.listeners.indexOf(listener)
      if (index > -1) {
        this.listeners.splice(index, 1)
      }
    }
  }

  private notifyListeners(): void {
    this.listeners.forEach(listener => listener(this.state))
  }
}

🎯 学习总结与下一步

核心知识回顾

通过第二天的学习,我们深入掌握了:

类型创建技术:

  • 泛型编程:类型参数化和约束
  • keyof 运算符:键名提取和联合类型
  • typeof 运算符:类型查询和提取
  • 索引访问类型:精确的类型访问
  • 条件类型:类型级别的逻辑判断
  • 映射类型:类型转换和变形
  • 模板字面量类型:字符串类型构建

实际应用能力:

  • 构建类型安全的工具函数
  • 设计可复用的类型工具
  • 实现复杂的类型推导
  • 创建类型安全的 API