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

如何简化TypeScript类型查找映射(Type Lookup Map)

简化TypeScript泛型函数实现的问题

需求与问题

我定义了一个记录类型Foo,用于关联名称和对应的值:

type Foo<Name extends keyof FooMap, Value> = {
  name: Name;
  value: Value;
};

同时有一个映射类型FooMap,键对应Foo的name属性,值对应Foo的value类型:

type FooMap = {
  a: {
    complex: string;
    type?: boolean;
  };
  b: string;
  c: number;
};

我尝试用以下泛型函数处理Foo实例,但value始终是所有可能类型的联合类型,无法在条件分支中正确窄化:

<Name extends keyof FooMap>({ name, value }: Foo<Name, FooMap[Name]>) => {
  if (name === 'a') {
    if (value.type ?? false) {
      strings.push(value.complex); // 类型错误:value可能是string/number,不存在type/complex属性
    }
  }

  if (name === 'b') {
    strings.push(value); // 类型错误:value可能是object/number,无法直接push到string数组
  }

  if (name === 'c') {
    while (strings.length < value) { // 类型错误:value可能是object/string,无法比较长度
      strings.push(strings[strings.length % Math.min(strings.length, value)]);
    }
  }
};

临时解决方案(存在冗余)

我通过修改FooMap直接存储Foo实例解决了类型窄化问题,但需要重复编写Foo的定义,代码冗余:

type FooMap = {
  a: Foo<'a', {
    complex: string;
    type?: boolean;
  }>;
  b: Foo<'b', string>;
  c: Foo<'c', number>;
};

<Name extends keyof FooMap>({ name, value }: FooMap[Name]) => {
  if (name === 'a') {
    if (value.type ?? false) {
      strings.push(value.complex);
    }
  }

  if (name === 'b') {
    strings.push(value);
  }

  if (name === 'c') {
    while (strings.length < value) {
      strings.push(strings[strings.length % Math.min(strings.length, value)]);
    }
  }
};

请问是否有办法避免这种代码重复?我是不是遗漏了TypeScript的关键概念?


解决方案:利用映射类型自动关联类型

你可以通过映射类型自动生成关联name和value的联合类型,无需重复定义:

步骤1:分离值类型映射

先定义只存储值类型的映射,避免和Foo耦合:

type ValueMap = {
  a: {
    complex: string;
    type?: boolean;
  };
  b: string;
  c: number;
};

步骤2:调整Foo类型定义

让Foo自动从ValueMap中获取对应值类型:

type Foo<Name extends keyof ValueMap> = {
  name: Name;
  value: ValueMap[Name];
};

步骤3:生成联合类型或使用泛型约束

方案A:使用联合类型作为参数类型

生成所有Foo类型的联合,TypeScript会自动识别条件分支中的类型:

type FooUnion = { [K in keyof ValueMap]: Foo<K> }[keyof ValueMap];

const processFoo = (foo: FooUnion) => {
  if (foo.name === 'a') {
    if (foo.value.type ?? false) {
      strings.push(foo.value.complex);
    }
  } else if (foo.name === 'b') {
    strings.push(foo.value);
  } else if (foo.name === 'c') {
    while (strings.length < foo.value) {
      strings.push(strings[strings.length % Math.min(strings.length, foo.value)]);
    }
  }
};

方案B:使用泛型函数

通过泛型参数约束,让TypeScript关联name和value的类型:

const processFoo = <K extends keyof ValueMap>(foo: Foo<K>) => {
  if (foo.name === 'a') {
    if (foo.value.type ?? false) {
      strings.push(foo.value.complex);
    }
  } else if (foo.name === 'b') {
    strings.push(foo.value);
  } else if (foo.name === 'c') {
    while (strings.length < foo.value) {
      strings.push(strings[strings.length % Math.min(strings.length, foo.value)]);
    }
  }
};

原理说明

当参数类型是Foo<K>(K为ValueMap的键)时,TypeScript会将name的字面量类型和value的类型绑定,通过if (foo.name === 'a')这样的类型守卫,就能自动将foo窄化为Foo<'a>,从而正确识别value的具体类型。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 23:10:43