一个函数是否可能有两个记录,其函数值具有与参数相同的参数?
Is it possible for a function to have two Records with function values with the same parameters as arguments?
我正在尝试创建一个可以接收两条记录的函数,其中:
- 第二个依赖于第一个并且两个记录具有相同的键
- 它们都具有作为值的函数,参数相同,但 return 类型不同。
我尝试了以下方法(及其许多变体):
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
是否可以提供:
- 支持对其 returned 值的类型推断(在上面的示例中:
repository
)
- 支持
mappers
的键类型(应与 obj
相同的键)
- 支持
mappers
记录右侧函数的参数,与obj
记录右侧函数的参数相同(如,如果 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;
这允许我创建一个具有参数 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 }
(如预期的那样)
我正在尝试创建一个可以接收两条记录的函数,其中:
- 第二个依赖于第一个并且两个记录具有相同的键
- 它们都具有作为值的函数,参数相同,但 return 类型不同。
我尝试了以下方法(及其许多变体):
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
是否可以提供:
- 支持对其 returned 值的类型推断(在上面的示例中:
repository
) - 支持
mappers
的键类型(应与obj
相同的键) - 支持
mappers
记录右侧函数的参数,与obj
记录右侧函数的参数相同(如,如果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;
这允许我创建一个具有参数 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 }
(如预期的那样)