TypeScript中基于属性值关联类型的类型映射方案问询
解决方案:通过类型映射与联合类型实现自动类型关联
你遇到的核心问题是混淆了TypeScript编译时类型和运行时值的边界——类型无法作为运行时对象的属性值存在。要避免几百个switch分支,我们可以通过编译时的类型映射+自动生成的联合类型来实现gameType与solutionKey的强关联,同时兼顾运行时验证的灵活性。
步骤1:定义编译时的类型映射表
首先创建一个枚举值到对应SolutionKey类型的映射,这是纯编译时的类型定义,不会生成运行时代码:
export enum GameType { WORDLE = "wordle", CROSSWORD = "crossword", // 新增枚举值直接在这里扩展即可 } export type WordleKey = { answer: string[]; }; export type CrosswordKey = { grid: number[]; }; // 核心:建立GameType到对应SolutionKey的编译时映射 type GameTypeToSolutionKey = { [GameType.WORDLE]: WordleKey; [GameType.CROSSWORD]: CrosswordKey; // 新增枚举值时,只需在这里添加对应的类型关联 };
步骤2:生成强关联的GameDocument联合类型
利用TypeScript的索引类型,自动生成每个GameType对应的强关联文档类型联合,替代原来的泛型定义:
// 自动生成每个GameType对应的文档类型,组成联合类型 export type GameDocument = { [K in keyof GameTypeToSolutionKey]: { gameType: K; solutionKey: GameTypeToSolutionKey[K]; }; }[keyof GameTypeToSolutionKey];
此时GameDocument等价于:
{ gameType: GameType.WORDLE; solutionKey: WordleKey } | { gameType: GameType.CROSSWORD; solutionKey: CrosswordKey }
TypeScript会自动根据gameType的值,对solutionKey进行类型窄化。
步骤3:运行时类型验证(可选但推荐)
如果需要从API获取未知类型的原始数据,我们可以创建一个运行时验证器映射,配合类型守卫函数实现安全的类型转换,无需写几百个switch:
// 运行时验证器:每个GameType对应一个验证函数,检查solutionKey的结构 const GameValidators = { [GameType.WORDLE]: (obj: unknown): obj is WordleKey => { return typeof obj === 'object' && obj !== null && 'answer' in obj && Array.isArray((obj as WordleKey).answer); }, [GameType.CROSSWORD]: (obj: unknown): obj is CrosswordKey => { return typeof obj === 'object' && obj !== null && 'grid' in obj && Array.isArray((obj as CrosswordKey).grid); }, // 新增枚举值时,只需在这里添加对应的验证函数 }; // 通用类型守卫函数:验证原始数据是否符合指定GameType的文档结构 function isGameDocument<T extends GameType>( gameType: T, obj: unknown ): obj is Extract<GameDocument, { gameType: T }> { if (typeof obj !== 'object' || obj === null) return false; const doc = obj as { gameType?: T; solutionKey?: unknown }; if (doc.gameType !== gameType) return false; return GameValidators[gameType](doc.solutionKey); }
实际使用示例
场景1:已知类型的断言
如果确定原始数据的类型,可以直接通过Extract断言:
// 从API获取的无类型对象 const wordleRawData = { gameType: GameType.WORDLE, solutionKey: { answer: ['T', 'E'] } } as unknown; // 断言为对应的GameDocument类型 const wordleDoc = wordleRawData as Extract<GameDocument, { gameType: GameType.WORDLE }>; wordleDoc.solutionKey.answer; // 类型正确,无TS报错
场景2:安全验证未知数据
const rawApiData: unknown = fetch('/api/game').then(res => res.json()); const gameType = (rawApiData as { gameType?: GameType }).gameType; if (gameType && isGameDocument(gameType, rawApiData)) { // 此处rawApiData的类型已被自动窄化,solutionKey类型完全匹配gameType if (rawApiData.gameType === GameType.WORDLE) { console.log(rawApiData.solutionKey.answer); } else if (rawApiData.gameType === GameType.CROSSWORD) { console.log(rawApiData.solutionKey.grid); } }
优势
- 扩展性极强:新增
GameType枚举值时,只需修改GameTypeToSolutionKey类型映射和GameValidators验证器,无需改动其他逻辑 - 完全避免大量
switch分支,代码维护成本极低 - TypeScript自动类型推断,无需手动维护泛型关联
内容的提问来源于stack exchange,提问作者Malice
相关产品推荐
相关产品推荐

