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
相关产品推荐
相关产品推荐

