Здесь происходит много вещей.
Прежде всего, вам действительно не нужны перегрузки: единственная разница в этих сигнатурах - это тип action
.Перегрузки полезны, когда несколько подписей отличаются согласованным образом;например, если тип возвращаемого значения функции зависит от типа action
, или если тип параметра state
зависит от типа action
.Поскольку ничто иное в сигнатурах не зависит от типа action
, вы можете получить то же поведение со стороны вызывающей стороны, если (как вы пытались) изменить action
на тип объединения (который, по сути, просто A
, посколькуA | (A & B) | (A & C) | (A & D)
является по существу эквивалентным - A
.)
Кроме того, ваши перегрузки на самом деле упорядочены от наименее до наиболее специфичных, что в обратном направлении от того, что вы обычно хотите.Подписи вызовов проверяются в порядке сверху вниз.Если вызов не соответствует первой подписи reducer(state: S, action: A): S
, он определенно не будет соответствовать ни одной из последующих подписей reducer(state: S, action: A & XYZ): S
.Это означает, что на практике будет использоваться только первая подпись.Если бы здесь были нужны перегрузки, я бы сказал, что сначала нужно указать более конкретные из них, и предоставить более подробную информацию о том, что делает что-то «конкретным».Но это на самом деле не имеет значения, потому что вам не нужны перегрузки.
Ваша проблема действительно заключается в реализации функции, где вы пытаетесь использовать оператор switch в качестве type guard на тип переменной action
.К сожалению, тип action
включает A
, параметр универсального типа, и TypeScript не выполняет сужение универсальных параметров .Было запрошено 1028 *, но, очевидно, такое сужение вызовет значительные проблемы с производительностью компилятора.Я предлагаю вам использовать определяемые пользователем защитные приспособления , чтобы лучше контролировать происходящее сужение.Это немного более многословно, но должно работать:
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;
};
Теперь реализация проверяет тип без ошибок, и сигнатуры вызовов упрощены до единого.Надеюсь, это поможет.Удачи!