Перегрузка функции обобщенных типов Typescript - PullRequest
0 голосов
/ 19 сентября 2018

Я пытался добавить перегрузки аргументов функций, начиная с самого конкретного, но сужение типов, похоже, не работает.Также попытался изменить аргументы на тип объединения, но охранники типов также не работают.Чего мне не хватает?

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;
};

1 Ответ

0 голосов
/ 19 сентября 2018

Здесь происходит много вещей.

Прежде всего, вам действительно не нужны перегрузки: единственная разница в этих сигнатурах - это тип 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;
};

Теперь реализация проверяет тип без ошибок, и сигнатуры вызовов упрощены до единого.Надеюсь, это поможет.Удачи!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...