Javascript 我应该如何在Typescript中定义一个动态类型,它应该保留用户输入的实际类型
我正在创建一个函数,它接受param中的某个对象,接口如下Javascript 我应该如何在Typescript中定义一个动态类型,它应该保留用户输入的实际类型,javascript,typescript,types,typescript-typings,Javascript,Typescript,Types,Typescript Typings,我正在创建一个函数,它接受param中的某个对象,接口如下{[someKey]:(data:any)=>any} type GenericRecord<value> = Record<string, value> function createFunctionObject (funcObject: GenericRecord<(someParam: GenericRecord<any>, randomKey: string) => any>
{[someKey]:(data:any)=>any}
type GenericRecord<value> = Record<string, value>
function createFunctionObject (funcObject: GenericRecord<(someParam: GenericRecord<any>, randomKey: string) => any>) {
return Object.fromEntries(
Object.entries(funcObject).map(([key, fuc]) => [
key,
(param: GenericRecord<any>) => fuc(param[randomKey])
])
);
}
这意味着我将得到3个函数作为返回{x,y,z}
,使用x,y,na
解构将抛出一个错误。此外,它应该保留x将返回一个数字,y,z将返回一个字符串。但是我得到的createFunctionObject
的返回类型签名是{[k:string]:(state:genericord)=>any;}
功能示例:
function createFunctionObject(funcObject,randomKey){
返回Object.fromEntries(
Object.entries(funcObject.map)([key,fuc])=>[
钥匙
(参数)=>fuc(参数[随机键])
])
);
}
const{a,b,c}=createFunctionObject({a:(arg)=>arg.a,b:(arg)=>arg.b,c:(arg)=>arg.c},'x')
log(a({x:{a:20}}))//20
log(b({x:{b:30}))//30
首先,让我们描述一下createFunctionObject
在类型级别应该做什么,而不必担心实现。给定类型为T
的输入funcObject
和类型为K
的输入randomKey
,其中T
表示其值为一个参数函数的对象,K
表示字符串
,createFunctionObject
的输出类型为FunctionObject
,定义如下:
type FunctionObject<T extends Record<keyof T, (arg: any) => any>, K extends string> =
{ [P in keyof T]:
T[P] extends (arg: infer A) => infer R ?
(arg: { [Q in K]: A }) => R : never
};
现在,虽然我们可以在类型级别表达此转换,但实际上不可能让编译器验证您的实现是否符合此类型签名。
Object.fromEntries()
、Object.entries()
和Array.prototype.map()
的标准库类型不够详细,因此即使我们为它们添加了自己的类型签名,我们也会发现编译器无法以避免编译器错误的方式组合它们。所以,让我们不要尝试这样做
相反,我们将告诉编译器不要过于担心验证实现。实现这一点的一种方法是在实施过程中:
function createFunctionObject<
T extends Record<keyof T, (arg: any) => any>,
K extends string
>(
funcObject: T,
randomKey: K
): FunctionObject<T, K> {
return Object.fromEntries(
Object.entries(funcObject).map(([key, fuc]) => [
key,
(param: Record<string, any>) => (fuc as Function)(param[randomKey])
]) // assert here
) as any; // assert here
}
注意,我必须将a
、b
和c
回调参数注释为{a:number}
、{b:number}
和{c:number}
;如果未将它们标记为a=>arg.a
,编译器将不知道如何推断它们,这将返回到any
。这是,所以最好在编译器无法推断的情况下进行注释
无论如何,您可以看到结果函数是可调用的,并且具有正确的类型签名:
/* const a: (arg: {
randomKey: {
a: number;
};
}) => number */
console.log(a({ randomKey: { a: 20 } })) // 20
/* const b: (arg: {
randomKey: {
b: number;
};
}) => number */
console.log(b({ randomKey: { b: 30 } })) // 30
看起来不错
是否适用于您的用例?请注意,如果在
arg
上没有某种类型的类型注释,就不能只编写(arg)=>arg.a
,除非您希望它的类型是any
。。。我写了(arg:{a:number})=>arg.a
,因为您似乎就是这样使用它的。此外,我无法判断您是否希望在其输入的a
或x
属性上调用输出函数,因为您的示例既有fuc(param.a)
又有fuc(param.x)
。请考虑将这里的代码修改为A,如果能满足您的需要,我会很乐意写一个答案。@ jCalz您的解决方案是完美的,<代码> t[k]扩展(ARG:推断A)=推断R?(arg:{x:A})=>R:never完成了所有的技巧。另外,我已经更新了代码段,可以对用户提供的任何随机键和对象调用输出函数。@jcalz,还有一个问题,在不同的声明中定义签名与在定义函数时定义签名有何不同。因为当我这样做时,返回的对象中会出现不兼容ok键的错误。据我所知,这就是我们重载签名的方式
function createFunctionObject<
T extends Record<keyof T, (arg: any) => any>,
K extends string
>(
funcObject: T,
randomKey: K
): FunctionObject<T, K> {
return Object.fromEntries(
Object.entries(funcObject).map(([key, fuc]) => [
key,
(param: Record<string, any>) => (fuc as Function)(param[randomKey])
]) // assert here
) as any; // assert here
}
// call signature
function createFunctionObject<
T extends Record<keyof T, (arg: any) => any>,
K extends string
>(
funcObject: T,
randomKey: K
): FunctionObject<T, K>;
// implementation (checked more loosely)
function createFunctionObject(
funcObject: Record<string, Function>,
randomKey: string
) {
return Object.fromEntries(
Object.entries(funcObject).map(([key, fuc]) => [
key,
(param: Record<string, any>) => fuc(param[randomKey])
])
);
}
const { a, b, c } = createFunctionObject({
a: (arg: { a: number }) => arg.a,
b: (arg: { b: number }) => arg.b,
c: (arg: { c: number }) => arg.c
}, "randomKey")
/* const a: (arg: {
randomKey: {
a: number;
};
}) => number */
console.log(a({ randomKey: { a: 20 } })) // 20
/* const b: (arg: {
randomKey: {
b: number;
};
}) => number */
console.log(b({ randomKey: { b: 30 } })) // 30