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

TypeScript同键异值对象的泛型消费类类型适配问题

TypeScript泛型适配多语言数据源的类型兼容问题

问题背景

我有一组存储源数据的JSON文件,每种语言对应一个文件,所有文件键名相同但值类型不同:

// source-en.json
{
  "action": ["change", "check"],
  "device": ["coil", "relay"]
}
// source-pl.json
{
  "action": ["wymienić", "sprawdzić"],
  "device": [
    {
      "gender": "f",
      "singular": "cewkę",
      "plural": "cewki"
    },
    {
      "gender": "m",
      "singular": "przekaźnik",
      "plural": "przekaźniki"
    }
  ]
}

我用以下TypeScript接口描述这些数据:

interface SourceEn {
  action: string[],
  device: string[],
}

interface SourcePl {
  action: string[],
  device: NounForms[],
}

interface NounForms {
  gender: 'm' | 'f' | 'n' | 'pl';
  singular: string;
  plural: string;
}

目前我实现了一组继承自BaseConsumer的消费类(如EnConsumer、PlConsumer),BaseConsumer负责获取源文件并转换JSON数据,具体子类处理语言差异。当前BaseConsumer的实现如下:

type Source = SourceEn | SourcePl;

class BaseConsumer<T extends Source> {
  public readonly action: Collection<ArrayItem<T['action']>>;
  public readonly device: Collection<ArrayItem<T['device']>>;

  public constructor(language: Language) {
    const data = this.getData(language);
    this.action = new Collection(data.action);
    this.device = new Collection(data.device); // 此处报错
  }
  
  protected getData(language: Language): T {...}
}

class Collection<T> {
  public constructor(private store: T[]) {}
}

type ArrayItem<A extends unknown[]> = A extends (infer E)[] ? E : never;

enum Language {
  EN = 'en',
  PL = 'pl',
}

错误信息

在this.device = new Collection(data.device);行出现两个错误:

  1. 参数类型不匹配:

参数类型string[] | NounForms[]无法赋值给类型string[]
类型NounForms[]无法赋值给类型string[]
类型NounForms无法赋值给类型string

  1. 属性类型不匹配:

类型Collection<string>无法赋值给类型Collection<Source['device']>
类型string无法赋值给类型Source['device']

问题根源在于T extends Source的约束本质是联合类型,即使子类指定了具体的SourcePl或SourceEn,TypeScript在处理BaseConsumer泛型时仍会保留联合类型的歧义,导致类型推断失败。我需要一个无需冗余条件类型、可扩展的泛型实现方案,以适配未来新增语言的需求。


解决方案

通过语言-数据源类型映射的方式,将具体语言与对应的数据源类型强绑定,消除联合类型带来的歧义,同时保证扩展性:

1. 定义语言-数据源映射类型

新增一个映射类型,明确每种语言对应的数据源接口,后续新增语言只需扩展此映射:

// 语言与数据源类型的绑定映射
type LanguageSourceMap = {
  [Language.EN]: SourceEn;
  [Language.PL]: SourcePl;
};

2. 重构BaseConsumer泛型逻辑

将泛型参数从T extends Source改为具体的语言枚举值,通过映射类型获取对应的数据源类型:

class BaseConsumer<L extends Language> {
  // 通过映射类型精准推断当前语言对应的字段类型
  public readonly action: Collection<ArrayItem<LanguageSourceMap[L]['action']>>;
  public readonly device: Collection<ArrayItem<LanguageSourceMap[L]['device']>>;

  public constructor(language: L) {
    const data = this.getData(language);
    // 此时data的类型是明确的LanguageSourceMap[L],无联合类型歧义
    this.action = new Collection(data.action);
    this.device = new Collection(data.device);
  }
  
  // getData返回的类型与当前语言严格绑定
  protected getData(language: L): LanguageSourceMap[L] {
    // 示例实现:根据语言加载对应JSON文件
    switch (language) {
      case Language.EN:
        return require('./source-en.json') as LanguageSourceMap[L];
      case Language.PL:
        return require('./source-pl.json') as LanguageSourceMap[L];
      default:
        throw new Error(`不支持的语言:${language}`);
    }
  }
}

3. 简化子类实现

子类只需指定对应的语言枚举值,无需重复定义类型:

class EnConsumer extends BaseConsumer<Language.EN> {}
class PlConsumer extends BaseConsumer<Language.PL> {}

扩展性说明

新增语言时只需完成三步:

  • 新增对应语言的JSON数据源文件
  • 新增对应的数据源接口(如SourceEs)
  • 在LanguageSourceMap中添加新的键值对,并扩展Language枚举

无需修改BaseConsumer的核心逻辑,完全符合开闭原则。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 16:43:13