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

TypeScript中如何根据Record值类型生成对应自定义键的输出Record

问题描述

现有convert函数,入参为对象,对象每个属性值支持两种类型:

  • 普通函数
  • 包含pending/fulfilled/rejected三个函数属性的对象

原入参类型定义存在语法错误(rejected: {} => any写法有误,无参函数正确写法为() => any),修正后基础类型如下:

type Input = {
  [K: string]: (() => any) | {
    pending: () => any,
    fulfilled: () => any,
    rejected: () => any
  }
}

需要实现返回值类型的动态推导,规则为:

  • 入参属性值为普通函数时,返回值保留原属性名,值为原函数
  • 入参属性值为三状态对象时,返回值生成${原属性名}Pending、${原属性名}Fulfilled、${原属性名}Rejected三个属性,值分别对应原对象里的三个方法

预期类型效果示例:

const result = convert({
  sync() {},
  async: {
    pending() {},
    fulfilled() {},
    rejected() {}
  }
});

// 以下调用均需通过类型校验
result.sync();
result.asyncPending();
result.asyncFulfilled();
result.asyncRejected();

现有仅支持固定后缀拼接的写法无法满足分支判断、一对多生成键的需求:

type Result<Input> = {
  [key in keyof Input as `${string & key}Fulfilled`]: any
}
实现方案

核心通过分布式条件类型、模板字面量类型、联合转交叉三个TypeScript类型特性实现,完整代码如下:

// 定义入参合法值类型
type InputValue = (() => any) | {
  pending: () => any
  fulfilled: () => any
  rejected: () => any
}
type ConvertInput = Record<string, InputValue>

// 单个入参键的转换逻辑:根据值类型生成对应的键值对对象
type ProcessSingleKey<K extends PropertyKey, V> = V extends () => any
  // 值为函数时,保留原键
  ? Record<K, V>
  // 值为三状态对象时,生成三个带后缀的键,对应各自的方法
  : V extends { pending: infer P, fulfilled: infer F, rejected: infer R }
    ? K extends string
      ? Record<`${K}Pending`, P>
        & Record<`${K}Fulfilled`, F>
        & Record<`${K}Rejected`, R>
      : never
    : never

// 工具类型:联合类型转交叉类型
type UnionToIntersection<U> =
  (U extends any ? (x: U) => void : never) extends (x: infer I) => void ? I : never

// 最终返回值类型
type ConvertResult<T extends ConvertInput> = UnionToIntersection<{
  [K in keyof T]: ProcessSingleKey<K, T[K]>
}[keyof T]>

// convert函数类型定义
declare function convert<T extends ConvertInput>(input: T): ConvertResult<T>
效果验证

用示例代码测试可得到符合预期的类型推导,甚至能保留每个方法的参数、返回值类型:

const result = convert({
  sync() { return 'sync res' },
  async: {
    pending() { return 'pending res' },
    fulfilled(res: number) { return res },
    rejected(err: Error) { return err.message }
  }
})

// 类型推导完全正确
result.sync() // 推导类型为 () => string
result.asyncPending() // 推导类型为 () => string
result.asyncFulfilled(123) // 推导类型为 (res: number) => number
result.asyncRejected(new Error('test')) // 推导类型为 (err: Error) => string
实现原理
  • 普通映射类型仅支持一个输入键映射为一个输出键,因此先将每个输入键单独转换为独立的对象类型:值为函数时生成单键对象,值为三状态对象时生成三键交叉对象
  • 通过索引访问[keyof T]拿到所有单属性转换结果的联合类型
  • 用UnionToIntersection工具将联合类型合并为单个对象类型,得到最终的返回结构

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 03:39:36