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

Is it possible for a function to have two Records with function values with the same parameters as arguments?

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

我尝试了以下方法(及其许多变体):

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

将发生以下情况:

withGenericLogging是否可以提供:

解决方案

我找到了我的问题的解决方案(经过多次迭代):

第 1 部分:定义通用 MapperFunction 类型

我需要推断 mappers 对象的每个键的函数值或参数,并使用它们来创建映射函数。所以我做了这个:

type MapperFunction<T> = T extends (...args: infer Params) => unknown
  ? (...args: Params) => unknown
  : (arg: T) => unknown;

这允许我创建一个具有参数 Params 或给定 T 的函数,当给定 MapperFunction.

的参数时

这允许我创建映射函数,例如:

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 和映射器函数作为值定义通用对象)通过执行以下操作解决了:

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 }(如预期的那样)