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

如何实现Redux Toolkit式builder模式构建无类型擦除的强类型Record

解法

要实现完全无类型擦除的builder模式强类型Map构建,核心是利用TypeScript的泛型交叉累积能力,配合satisfies关键字保留原始record类型,以下是无冗余、无类依赖的标准函数式实现:

完整可运行代码

// 1. 基础类型定义
type BaseRecord<Name extends string = string> = {
  name: Name
} & Record<string, string | number>

// 2. Builder类型,泛型T存储当前已累积的RecordMap类型
type RecordMapBuilder<T extends Record<string, BaseRecord> = {}> = {
  addRecord: <R extends BaseRecord>(record: R) => RecordMapBuilder<T & Record<R['name'], R>>
}

// 3. 核心构造函数实现
function createRecordMap<T extends Record<string, BaseRecord>>(
  builderCallback: (builder: RecordMapBuilder<{}>) => RecordMapBuilder<T>
): T {
  const recordMap: Record<string, BaseRecord> = {}
  const builder: RecordMapBuilder = {
    addRecord(record) {
      recordMap[record.name] = record
      return builder
    }
  }
  // 执行构造逻辑,运行时结构和类型完全对齐
  builderCallback(builder)
  return recordMap as T
}

使用示例

// 注意:用satisfies而非显式标注BaseRecord,避免类型擦除
const recordA = {
  name: "recordA",
  fooString: "foo",
  fooNumber: 6
} satisfies BaseRecord

const recordB = {
  name: "recordB",
  barString: "bar",
  barNumber: 2,
  barSecondString: "bar2"
} satisfies BaseRecord

// 链式调用构建Map
const newRecordMap = createRecordMap(builder => 
  builder.addRecord(recordA).addRecord(recordB)
)

类型校验结果

  • newRecordMap.recordA.fooString 类型自动推断为string
  • newRecordMap.recordB.barSecondString 类型自动推断为string
  • 访问不存在的key(如newRecordMap.nonExist)会直接触发TS类型报错,完全符合预期。

原代码问题说明

  1. 之前的泛型设计错误:将createRecordMap的泛型绑定到builder实例本身,TS无法从回调的副作用调用中反向推断累积类型,改为从builder返回值的泛型中提取最终Map类型即可解决。
  2. 显式标注recordA: BaseRecord会拓宽类型,使用satisfies关键字可以在校验类型合法性的同时保留原始字面量类型。
  3. 唯一的类型断言recordMap as T是完全安全的,运行时结构和泛型定义100%对齐。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 02:36:04