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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 14:05:25