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

如何在TypeScript中为静态类型函数Record传入参数

解决TypeScript中函数工厂对象的精确类型映射问题

问题场景

我有一组有限的函数工厂,每个工厂返回一个CRUD操作(返回类型为Promise)。希望将静态类型的函数构造器对象,转换为构造器执行后的函数组成的静态类型对象:

输入类型:

{ f1: (k: K) => (...) => Promise<...>, f2: (k: K) => (...) => Promise<...> }

传入K类型的key后,期望输出类型:

{ f1: (...) => Promise<...>, f2: (...) => Promise<...> }

具体示例

type K = 'a' | 'b';
type DbRecord<T extends K> = { id: string; type: T }; // 补充DbRecord类型定义

const makeGet = (k: K): (id: string) => Promise<DbRecord<K>> => 
  (id) => Promise.resolve({ id, type: k });
const makeSet = (k: K): (id: string, data: Partial<DbRecord<K>>) => Promise<void> => 
  (id, data) => Promise.resolve();

const fs = { get: makeGet, set: makeSet } as const;

// 目标:makeCrud返回精确的键值类型映射,而非联合类型
export const makeCrud = (k: K) => mapValues(fs, (f) => f(k));

现有问题:使用lodash的mapValues或fp-ts的Record.map时,返回类型会变成所有工厂返回值的联合类型(比如{ get: ReturnType<typeof makeGet> | ReturnType<typeof makeSet>, set: ... }),无法保持键与对应工厂返回值的精确关联。


解决方案

方案1:手动实现类型安全的mapValues函数

通过TypeScript泛型和映射类型,自定义一个能保留键值类型关联的mapValues:

type K = 'a' | 'b';
type DbRecord<T extends K> = { id: string; type: T };

const makeGet = (k: K): (id: string) => Promise<DbRecord<K>> => 
  (id) => Promise.resolve({ id, type: k });
const makeSet = (k: K): (id: string, data: Partial<DbRecord<K>>) => Promise<void> => 
  (id, data) => Promise.resolve();

const fs = { get: makeGet, set: makeSet } as const;

// 自定义类型安全的mapValues
function mapValues<T extends Record<string, (...args: any[]) => any>, Arg>(
  obj: T,
  arg: Arg
): { [Key in keyof T]: ReturnType<T[Key]> } {
  const result = {} as { [Key in keyof T]: ReturnType<T[Key]> };
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      result[key] = obj[key](arg);
    }
  }
  return result;
}

export const makeCrud = (k: K) => mapValues(fs, k);

// 类型推导验证
const crudA = makeCrud('a');
// crudA.get 类型:(id: string) => Promise<DbRecord<'a'>>
// crudA.set 类型:(id: string, data: Partial<DbRecord<'a'>>) => Promise<void>

这个函数通过泛型T捕获输入对象的精确类型,再用映射类型{ [Key in keyof T]: ReturnType<T[Key]> }为每个键单独推导对应的工厂返回值类型,完全保留键值的类型关联。

方案2:修复fp-ts Record.map的类型推导

如果坚持使用fp-ts,可以通过手动指定返回类型的方式,避免联合类型的问题:

import { map as recordMap } from 'fp-ts/Record';

type K = 'a' | 'b';
type DbRecord<T extends K> = { id: string; type: T };

const makeGet = (k: K): (id: string) => Promise<DbRecord<K>> => 
  (id) => Promise.resolve({ id, type: k });
const makeSet = (k: K): (id: string, data: Partial<DbRecord<K>>) => Promise<void> => 
  (id, data) => Promise.resolve();

const fs = { get: makeGet, set: makeSet } as const;

export const makeCrud = (k: K) => 
  recordMap((f) => f(k))(fs) as { [Key in keyof typeof fs]: ReturnType<typeof fs[Key]> };

这里通过类型断言强制TypeScript使用每个键对应的工厂返回值类型,而非合并为联合类型。


关键要点

  • 必须用as const断言输入对象fs的类型,确保TypeScript捕获每个属性的精确函数类型,而非宽泛的函数类型。
  • 核心逻辑是利用映射类型和ReturnType工具类型,为每个键单独推导返回类型,避免类型合并。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 13:09:26