typescript 泛型函数重载

标签 typescript redux

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

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 equivalentA 。)

顺便说一句,您的重载实际上是从最具体到最具体的顺序排列的,这与您通常想要的相反。调用签名按从上到下的顺序检查。如果调用不匹配第一个签名 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/

相关文章:

javascript - React-Redux 设计方法——用于可编辑文本字段双向绑定(bind)的 Redux Action 或本地函数

javascript - 如何在 Typescript 中将 safeStringify() 添加到 JSON?

javascript - typescript 中 switch 的替代方案

javascript - 如何模拟 axios 类

reactjs - [RN][Redux-Persist] AutoReHydrate 不是一个函数

javascript - 在 Redux reducer 中添加到无名数组之前

javascript - Typescript 从其他 ts 文件扩展类

typescript - 如何创建具有动态字段名称的 Formik YUP 架构?

Angular2 Component @Input 双向绑定(bind)

javascript - 调用了 mapStateToProps,但未调用 componentDidUpdate