MST
星途 面试题库

面试题:TypeScript名义类型在复杂类型系统中的应用

在一个大型的TypeScript项目中,有多个模块依赖于不同的名义类型,例如订单模块有OrderId(名义类型为字符串),用户权限模块有PermissionId(名义类型为数字)。请阐述如何在保持类型安全性的同时,高效地管理和复用这些名义类型,并且在模块之间传递数据时避免类型混淆。
31.3万 热度难度
前端开发TypeScript

知识考点

AI 面试

面试题答案

一键面试
  1. 使用接口和类型别名定义名义类型
    • 对于OrderId,可以这样定义:
    type OrderId = string & { readonly __orderIdBrand: unique symbol };
    
    • 对于PermissionId,定义为:
    type PermissionId = number & { readonly __permissionIdBrand: unique symbol };
    
    • 通过这种方式,每个名义类型都有自己独特的“品牌”,即使它们底层的数据类型相同,TypeScript 也能区分它们。
  2. 封装操作函数
    • 为每个名义类型创建专门的操作函数,例如对于OrderId
    function createOrderId(id: string): OrderId {
      return id as OrderId;
    }
    function getOrderIdValue(id: OrderId): string {
      return id;
    }
    
    • 对于PermissionId类似:
    function createPermissionId(id: number): PermissionId {
      return id as PermissionId;
    }
    function getPermissionIdValue(id: PermissionId): number {
      return id;
    }
    
    • 这样在不同模块中操作这些名义类型时,都通过这些封装的函数,保证类型的正确使用。
  3. 模块间数据传递
    • 在模块间传递数据时,严格按照定义的名义类型进行参数和返回值的类型标注。例如,如果一个函数接收OrderId
    function processOrder(orderId: OrderId) {
      // 处理订单逻辑
    }
    
    • 调用该函数时,确保传入的是OrderId类型,而不是普通的string。这样可以避免类型混淆。
  4. 使用泛型增强复用性
    • 如果有一些通用的逻辑适用于不同的名义类型,可以使用泛型来实现复用。例如,假设有一个缓存函数:
    type NominalType<T> = T & { readonly __brand: unique symbol };
    const cache: Record<string, any> = {};
    function cacheValue<T extends NominalType<any>>(key: string, value: T): T {
      if (!cache[key]) {
        cache[key] = value;
      }
      return cache[key] as T;
    }
    
    • 这个函数可以用于缓存不同的名义类型的值,同时保持类型安全性。