我正在尝试创建一个可以接收两条记录的函数,其中:
- 第二个依赖于第一个,并且两个记录具有相同的密钥
- 它们都将函数作为具有相同参数但返回类型不同的值
我尝试过以下(及其许多变体(:
export const withGenericLogging = (
logger: Logger,
component: string,
subcomponent: string,
) => <
Keys extends string,
ObjFn extends (...args: any[]) => unknown,
Obj extends Record<Keys, ObjFn>,
Mappers extends Record<Keys, (...args: Parameters<ObjFn>) => unknown>,
>(
obj: Obj,
mappers: Mappers,
) =>
Object.keys(obj).reduce<Obj>(
(acc, key) => {
if (typeof obj[key] !== 'function') {
return acc;
}
const mapper = mappers?.[key]
acc[key] = withLogging(
logger,
component,
subcomponent ? `${subcomponent}.${key}` : key,
)(obj[key], mapper);
return acc;
},
{ ...obj },
);
注:withLogging
、Logger
和logger
的实现与此问题无关。
这很好,因为打字脚本知道,如果我做以下操作:
const create = (id: string) => 'value'
const repository = withGenericLogging(
log,
'Module',
'Submodule'
)(
{ create },
{ create: id => 'mappedId' },
);
将发生以下情况:
repository
将完全继承obj
的类型(按照预期,它将是类型{ create: (id: string) => string }
(- IDE将为
mappers
参数提供某种类型的支持({ create: id => 'mappedId' }
中的create
键是IDE建议的( id => 'mappedId'
中的id
属于any
类型,应该属于string
类型
withGenericLogging
是否可以提供:
- 支持对其返回值进行类型推断(在上面的示例中:
repository
( - 支持
mappers
的密钥类型(应与obj
的密钥相同( - 支持
mappers
Record右侧函数的参数,该参数应与obj
Record右侧函数相同(如中所示,如果obj
为{ create: (id: string) => Promise<void> }
,则mappers
应为{ create: (id: string) => unknown }
( - 只有一种通用的处理类型的方式,而不是固定的(
obj
应该支持任何类型的Record<string, (...args: unknown[]) => unknown>
,而不是一个固定的type Something = { create: (id: string) => Promise<void> }
(
解决方案
我已经找到了我的问题的解决方案(经过多次迭代(:
第1部分:定义通用MapperFunction类型
我需要推断mappers
对象的每个键的函数值或参数,并使用它们来创建映射函数。所以我做了这个:
type MapperFunction<T> = T extends (...args: infer Params) => unknown
? (...args: Params) => unknown
: (arg: T) => unknown;
这允许我创建一个函数,当给MapperFunction
一个参数时,该函数具有参数Params
或给定的T
。
这允许我创建映射函数,例如:
const create = (id: string) => 'value'
const value = 'value'
const mapperFunctionForCreate: MapperFunction<typeof create> = id => ({ mappedId: id })
const mapperFunctionForValue: MapperFunction<typeof value> = arg => ({ mappedId: arg })
其中:
mapperFunctionForCreate
的类型为(id: string) => unknown
(如预期(mapperFunctionForValue
的类型为(arg: string) => unknown
(如预期(
这解决了我的第一个问题。
第2部分:定义通用MapperFunctionsObject类型
我的问题的第二部分(用键Keys
和mapper函数定义一个通用对象作为值(通过以下操作解决:
type MapperFunctionsObject<T> = T extends Record<string | number | symbol, unknown>
? { [P in keyof T]: MapperFunction<T[P]> }
: never;
这使我可以执行以下操作:
const obj = {
create: (id: string) => 'value'
};
const mapperFunctionsObject: MapperFunctionsObject<typeof obj> = {
create: id => ({ mappedId: id })
};
其中mapperFunctionsObject.create
的类型为(id: string) => unknown
(如预期(。
这使我能够组合以下功能:
type MapperFunction<T> = T extends (...args: infer Params) => unknown
? (...args: Params) => unknown
: (arg: T) => unknown;
type MapperFunctionsObject<T> = T extends Record<string | number | symbol, unknown>
? { [P in keyof T]: MapperFunction<T[P]> }
: never;
export const withGenericLogging = (
logger: Logger,
component: string,
subcomponent?: string,
) => <
Key extends string,
Value,
Obj extends Record<Key, Value>,
MapperObj extends MapperFunctionsObject<Obj>
>(
obj: Obj,
mappers?: Partial<MapperObj>,
) =>
Object.keys(obj).reduce<Obj>(
(acc, key) => {
if (typeof obj[key] !== 'function') {
return acc;
}
const func = obj[key];
const mapper = mappers?.[key];
acc[key] = withLogging(
logger,
component,
subcomponent ? `${subcomponent}.${key}` : key,
)(func, mapper);
return acc;
},
{ ...obj },
);
这正是我需要做的,比如当我做以下事情时:
const create = (id: string) => 'value'
const repository = withGenericLogging(
log,
'Module',
'Submodule'
)(
{ create },
{ create: id => 'mappedId' },
);
{ create: id => 'mappedId' }
的类型将被正确推断,并且将是{ create: (id: string) => unknown }
(如预期(