![](/img/trans.png)
[英]How to infer function return type based on an function array argument?
[英]How to infer an external function's argument type
type GlobalState = {
yourDetails: {
firstName: string,
lastName: string,
}
}
const globalState = {
yourDetails: {
firstName: 'test',
lastName: 'test1',
}
}
type Action<T> = (state: GlobalState, payload: any) => T;
type Actions<T> = Record<string, Action<T>>;
type ActionOutput<T extends (...arg: any[]) => any> = T extends (arg: any, arg1: infer G) => any ? (payload: G) => any : (payload: any) => any;
type ActionsOutput<T> = Record<keyof T, ActionOutput<T>>; // how to pass acording function type?
function generator<T, G extends Actions<T>>(payload: G): {
actions: ActionsOutput<G>
} {
return {
actions: Object.entries(payload).reduce((prev, [key, callback]) => {
return {
...prev,
[key]: (actionPayload) => {
callback(globalState, actionPayload);
},
}
}, {} as ActionsOutput<G>),
};
}
function updateNameAction(state: GlobalState, payload: {
firstName: string,
lastName: string,
}) {
return {
...state,
yourDetails: {
...payload,
}
}
}
const { actions } = generator<GlobalState, { updateNameAction: any }>({
updateNameAction
})
actions.updateNameAction({
test: '123'
})
如何infer
函数的参数。 因此,当调用上述示例的action
时,如果有效负载的类型不匹配,它将引发错误。
所以我会试着在这里解释得更好一点。
使用 TypeScript 时,您必须记住它在运行时几乎没有任何内容。 所以你可以定义所有漂亮的类型和泛型,但是当你运行代码时它们就消失了!
你唯一可以坚持的是类。 这些映射到 JavaScript,但即使在这种情况下,“智能转换”也消失了,所以:
class MyClass {
constructor(readonly value: string){};
}
// the definitions below both work in TS
const correctInstantiation = new MyClass('the value');
const wrongInstantiation = { value: 'the other value' };
console.log(correctInstantiation instanceof MyClass); // this logs true
console.log(wrongInstantiation instanceof MyClass); // this logs false
如您所见,我使用构造函数定义了类并添加了readonly
(但您也可以使用public
和private
),这样构造函数参数作为类字段工作,您可以在创建实例时直接传递它们。
这是即使在像 Java这样的全类型语言中也做不到的事情。 由于在运行时 Generic 消失了,您需要以某种方式显式传递实际类型,因此:
// this just won't compile
function wrongFunction<T>(payload: T) {
return payload instanceof T; // can't do this!!
}
因此,对于您的情况,我能想到的唯一解决方案是在包装器中定义Actions
,该包装器还声明了payload
类型,例如:
class FunnyPayload {
constructor(readonly pun: string) {}
}
class BoringPayload {
constructor(readonly remark: string) {}
}
type ActionDefinition<PayloadType, OutputType> = {
payloadType: any,
action: Action<PayloadType, OutputType>,
}
const actionsDictionary = {
be_funny: {
payloadType: FunnyPayload,
action: (payload: FunnyPayload) => {
console.log(`Funny Guys says: ${payload.pun}`);
console.log(`ONG so funny!`);
}
},
be_boring: {
payloadType: BoringPayload,
action: (payload: BoringPayload) => {
console.log(`Boring Guys says: ${payload.remark}`);
console.log(`ONG so boring!`);
}
},
} as { [key: string]: ActionDefinition<any, any> };
在这里,我为每个可能的payload
创建了特定的类(您也可以使用原始类型,例如number
和string
,但对于更复杂的对象,您需要一个类,请记住智能转换问题!)
然后,如果您想生成带有有效负载检查的Action
列表,您可以执行以下操作:
const generatedActions: Action<any, any>[] = Object.keys(actionsDictionary).map(key => {
const definition = actionsDictionary[key]
return (payload: any) => {
if(!(payload instanceof definition.payloadType)) throw new Error(`Payload mismatch!`);
return definition.action(payload);
}
});
现在您将拥有休闲场景:
generatedActions[0](new FunnyPayload('** something silly! **')); // works
generatedActions[1](new BoringPayload('** something serious... **')); // works
generatedActions[0](new BoringPayload('** something serious... **')); // nope! wrong playload!
generatedActions[0]({ pun: '** something hilarious! **' }); // nope! no instantiation!
最后只是个人品味提示:当您定义泛型时,只需给它们一个更具解释性的名称,例如PayloadType
和OutputType
这样您将始终知道您指的是什么,像T
、 V
等简单的字母。越是泛型越容易混淆添加!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.