一个函数是否可能有两个函数值与参数相同的记录



我正在尝试创建一个可以接收两条记录的函数,其中:

  • 第二个依赖于第一个,并且两个记录具有相同的密钥
  • 它们都将函数作为具有相同参数但返回类型不同的值

我尝试过以下(及其许多变体(:

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 },
);

注:withLoggingLoggerlogger的实现与此问题无关。

这很好,因为打字脚本知道,如果我做以下操作:

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的密钥相同(
  • 支持mappersRecord右侧函数的参数,该参数应与objRecord右侧函数相同(如中所示,如果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 }(如预期(

最新更新