我试图从最具体的开始添加函数参数重载,但类型缩小似乎不起作用。还尝试将参数更改为联合类型,但类型保护也不起作用。我错过了什么?
type IReducer<S, A> = (state: S, action: A) => S;
interface IAsyncHandlers<S, A extends IAction> {
request?: IReducer<S, A>;
success?: IReducer<S, A>;
failure?: IReducer<S, A & { payload: any; error: true }>;
}
interface IAction {
type: string;
payload: any;
}
const getActionHandler = <S, A>(handler?: IReducer<S, A>) => (state: S) => (action: A): S =>
handler ? handler(state, action) : state;
const handleAsyncAction = <S, A extends IAction>(handlers: IAsyncHandlers<S, A>): IReducer<S, A> => {
function reducer(state: S, action: A): S
function reducer(state: S, action: A & { error: true }): S;
function reducer(state: S, action: A & { meta: { isPending: true } }): S;
function reducer(state: S, action: A & { error?: any; meta?: any } ): S {
switch (true) {
case action.error:
// Property 'error' is optional in type 'IAction & { error?: any; meta?: any; }'
// but required in type '{ payload: any; error: true; }'.
return getActionHandler(handlers.failure)(state)(action);
case action.meta && action.meta.isPending:
return getActionHandler(handlers.request)(state)(action);
default:
return getActionHandler(handlers.success)(state)(action);
}
}
return reducer;
};
最佳答案
这里发生了很多事情。
首先,您真的不需要重载:这些签名的唯一区别是action
的类型。当多个签名以某种协调的方式不同时,重载很有用;例如,如果函数返回值的类型取决于 action
的类型,或者如果 state
参数类型取决于 action< 的类型
。由于签名中的任何其他内容都取决于 action
的类型,您可以通过(如您尝试的那样)将 action
更改为联合类型,从调用方获得相同的行为(本质上只是 A
,因为 A | (A & B) | (A & C) | (A & D)
是 essentially equivalent 到 A
。)
顺便说一句,您的重载实际上是从最具体到最具体的顺序排列的,这与您通常想要的相反。调用签名按从上到下的顺序检查。如果调用不匹配第一个签名 reducer(state: S, action: A): S
,它肯定不会匹配任何后续签名 reducer(state: S, action: A & XYZ): S
。这意味着在实践中只会使用第一个签名。如果这里需要重载,我会告诉你把更具体的放在第一位,并提供更多关于是什么让某些东西变得“具体”的细节。但这并不重要,因为您不需要重载。
您的问题实际上是在函数的实现内部,您尝试将 switch 语句用作 type guard action
变量的类型。不幸的是,action
的类型涉及A
,一个通用类型参数,和TypeScript does not do narrowing on generic parameters。 .它已被请求 ( microsoft/TypeScript#24085 ),但显然这种缩小会导致编译器出现严重的性能问题。我给你的建议是使用 user-defined type guards对发生的缩小施加更多控制。它有点冗长,但它应该可以工作:
const isErrorAction =
<A extends IAction & { error?: any }>(a: A): a is A & { error: true } =>
(a.error)
const isRequestAction =
<A extends IAction & { meta?: { isPending?: any } }>(
a: A
): a is A & { meta: { isPending: true } } =>
(a.meta && a.meta.isPending);
const handleAsyncAction = <S, A extends IAction>(
handlers: IAsyncHandlers<S, A>
): IReducer<S, A> => {
function reducer(state: S, action: A): S {
if (isErrorAction(action)) {
return getActionHandler(handlers.failure)(state)(action);
}
// not strictly necessary to narrow here, but why not
if (isRequestAction(action)) {
return getActionHandler(handlers.request)(state)(action);
}
return getActionHandler(handlers.success)(state)(action);
}
return reducer;
};
现在实现类型检查没有错误,并且调用签名已简化为单个签名。
关于 typescript 泛型函数重载,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52404077/