You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TypeScript类型守卫中对象到对象映射的更优实现咨询

核心结论

TypeScript 是结构化类型系统,不存在通用的原生StringOf<T>工具可以直接拿到类型的声明名称——如果两个类型结构完全一致,就算声明时名字不同,TS 也会判定为同一类型,根本无法区分。

你当前的实现思路是对的,但完全可以把重复的条件判断逻辑收敛,不用写一长串嵌套extends三元。


方案1:集中注册表模式(适配第三方类型,最通用)

如果你的Article/User是类似 Prisma 这种第三方库生成的、不能直接修改类型定义,只需要维护一份统一的模型注册表,所有映射逻辑从注册表自动推导即可,后续新增模型只需要改注册表一处:

import type { Prisma, Article, User } from '@prisma/client'

// 所有模型和名称的绑定只需要在这里维护一次
interface ModelRegistry {
  Article: Article
  User: User
}

// 自动推导所有模型的联合类型,等价于 type Model = Article | User
type Model = ModelRegistry[keyof ModelRegistry]

// 自动从模型反推模型名,不需要手写任何extends条件
type ModelName<M extends Model> = {
  [K in keyof ModelRegistry]: ModelRegistry[K] extends M ? K : never
}[keyof ModelRegistry]

// 选项映射表和注册表对齐即可
interface ModelOptionMap {
  Article: Prisma.ArticleSelect
  User: Prisma.UserSelect
}

// 推导最终选项类型
type Options<M extends Model> = ModelOptionMap[ModelName<M>]

// 用法和你之前完全一致
const options: Options<User> = {}

如果想进一步减少重复代码,甚至可以把选项配置也合并到注册表里,不用维护两个独立接口:

interface ModelRegistry {
  Article: {
    model: Article
    options: Prisma.ArticleSelect
  }
  User: {
    model: User
    options: Prisma.UserSelect
  }
}

// 所有类型都从单张表推导,零重复逻辑
type Model = ModelRegistry[keyof ModelRegistry]['model']
type ModelName<M extends Model> = {
  [K in keyof ModelRegistry]: ModelRegistry[K]['model'] extends M ? K : never
}[keyof ModelRegistry]
type Options<M extends Model> = ModelRegistry[ModelName<M>]['options']

方案2:品牌标记模式(自定义类型可用,实现原生StringOf效果)

如果模型类型是你自己定义的,可以通过给类型加仅类型层面存在的品牌标记,实现你要的StringOf<T>效果,不需要额外维护注册表:

// 定义唯一的品牌标记,编译后会完全擦除,不影响运行时
declare const MODEL_TYPE: unique symbol
type NamedModel<T extends string> = { [MODEL_TYPE]: T }

// 定义模型时带上标记
type Article = NamedModel<'Article'> & { id: number; content: string }
type User = NamedModel<'User'> & { id: number; name: string }

// 实现你要的StringOf工具
type StringOf<T extends NamedModel<string>> = T[typeof MODEL_TYPE]

// 直接用StringOf做映射
interface ModelOptionMap {
  Article: Prisma.ArticleSelect
  User: Prisma.UserSelect
}
type Options<M extends NamedModel<string>> = ModelOptionMap[StringOf<M>]

// 测试:类型推导完全正确
const userOptions: Options<User> = {}
const articleOptions: Options<Article> = {}

如果不介意运行时多一个字段,也可以直接给类型加一个字面量类型的__type字段,不需要用unique symbol,逻辑更直观。


选型建议

  • 用 Prisma 这类自动生成模型类型的场景,选方案1,维护成本最低,不需要修改生成的类型代码
  • 全项目自定义模型类型的场景,选方案2,类型链路最短,不需要额外维护映射表

内容的提问来源于stack exchange,提问作者shtse8

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 10:15:30