Typescript泛型函数重载

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Typescript泛型函数重载相关的知识,希望对你有一定的参考价值。

我试图添加函数参数重载从最具体的开始,但类型缩小似乎不起作用。还尝试将参数更改为联合类型,但类型保护也不起作用。我错过了什么?

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更改为union类型(基本上只是A,因为A | (A & B) | (A & C) | (A & D)essentially equivalentA。 )

顺便说一下,你的重载实际上是从最少到最具体的顺序排序,这是你通常想要的倒退。呼叫签名按从上到下的顺序进行检查。如果一个电话与第一个签名reducer(state: S, action: A): S不匹配,它绝对不会匹配任何后续签名reducer(state: S, action: A & XYZ): S。这意味着只有第一个签名才能在实践中使用。如果这里需要重载,我会告诉你先把更具体的内容放在首位,然后提供更多有关“具体”内容的细节。但这并不重要,因为您不需要重载。

您的问题实际上在函数的实现中,您尝试在type guard变量的类型上使用switch语句作为action。不幸的是,action的类型涉及A,一个通用的类型参数,和TypeScript does not do narrowing on generic parameters。它一直是requested,但显然这样的缩小会导致编译器出现严重的性能问题。我的建议是使用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泛型函数重载的主要内容,如果未能解决你的问题,请参考以下文章

打字稿、泛型和重载

React Typescript 泛型类型重载(例如 useState)

TypeScript——泛型

TypeScript泛型

难以理解的打字稿泛型函数重载

如何告诉 TypeScript 两个泛型类型是相同的?