如何在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
相关产品推荐
相关产品推荐

