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

TypeScript中带类型安全遍历映射类型Record生成对应实例的最佳方案

TypeScript中保持键值类型对应地遍历映射生成实例Record的解决方案

问题场景与复现

我有两个Record(选项Record和实例Record),它们的键与值存在对应关系,通过带泛型值的映射类型保证该对应性。任务是基于选项Record生成实例Record:普通for..in循环会将对象键类型降级为string并丢失键值对应,采用Array.forEach泛型回调仍无法正确设置类型。

问题复现代码:

type key = 'key1' | 'key2';
type someOption<T extends key> = `${T}`;
type someInstance<T extends key> = Array<`${T}`>;
type record1 = {
  [T in key]: someOption<T>;
};
type record2 = {
  [T in key]: someInstance<T>;
}

const rec1: record1 = {
  key1: 'key1',
  key2: 'key2',
};
const rec2: Partial<record2> = {};

Object.entries(rec1).forEach(<T extends key>([recKey, recValue]: [T, someOption<T>]) => {
  rec2[recKey] = [recValue, recValue]; // 此处出现类型错误
});

// 遍历完成后移除Partial约束
// return rec2 as record2;

错误信息

第18行TypeScript错误:

TS2322: Type ${T}[] is not assignable to type Partial[T]
Type ${T}[] is not assignable to type
someInstance<'key1'> & someInstance<'key2'>
Type ${T}[] is not assignable to type someInstance<'key1'>
Type ${T} is not assignable to type 'key1'
Type key is not assignable to type 'key1'
Type 'key2' is not assignable to type 'key1'

疑问点

解析后rec2[recKey]的类型变为Array<key1> & Array<key2>(交叉类型),而非预期的联合类型,不清楚为何会出现这种情况,也不确定当前实现是否有误。使用TypeScript 5.0.4和Node.js 16.19.1。

问题原因

TypeScript在处理索引类型为联合类型的访问时,默认会将结果推断为交叉类型而非联合类型。这是因为TypeScript无法在forEach的泛型回调中跟踪每个循环迭代的具体键类型,只能将recKey视为整个联合类型key,进而导致rec2[recKey]被推断为所有可能值类型的交叉(someInstance<'key1'> & someInstance<'key2'>),而交叉的数组类型实际上不可赋值(Array<'key1'>和Array<'key2'>无交集),因此抛出类型错误。

解决方案

方案1:使用类型断言(简单直接)

在赋值时对rec2[recKey]进行类型断言,明确告知TypeScript当前赋值的类型与键对应:

Object.entries(rec1).forEach(<T extends key>([recKey, recValue]: [T, someOption<T>]) => {
  rec2[recKey] = [recValue, recValue] as someInstance<T>;
});

该方式快速解决问题,适合简单场景,缺点是需要手动维护断言的准确性。

方案2:使用reduce代替forEach利用类型推断

reduce可通过返回值的类型推断更精准地维护键值对应关系,直接生成目标类型:

const rec2 = Object.entries(rec1).reduce((acc, [recKey, recValue]) => {
  acc[recKey as key] = [recValue, recValue] as someInstance<typeof recKey>;
  return acc;
}, {} as Partial<record2>) as record2;

方案3:手动遍历每个键(类型最安全)

如果键的数量较少,直接枚举每个键可完全避免类型推断问题:

const rec2: record2 = {
  key1: [rec1.key1, rec1.key1],
  key2: [rec1.key2, rec1.key2],
};

这种方式类型最安全,无任何类型断言,适合键集合固定且数量不多的场景。

方案4:封装类型安全的遍历工具函数

封装泛型工具函数,确保遍历过程中键值类型的对应性,提升复用性:

function mapRecord<K extends string | number | symbol, V, R>(
  record: Record<K, V>,
  mapper: (key: K, value: V) => R
): Record<K, R> {
  const result = {} as Record<K, R>;
  for (const key in record) {
    if (Object.prototype.hasOwnProperty.call(record, key)) {
      result[key] = mapper(key as K, record[key]);
    }
  }
  return result;
}

const rec2 = mapRecord(rec1, (key, value) => [value, value] as someInstance<typeof key>);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 22:05:11