无法识别功能类型

时间:2019-06-28 12:37:14

标签: typescript

以下代码正在使用reduxredux-thunk

type MyAction = Action<'action-type-1'>

export interface Action<T = any> {
  type: T
}

此代码没有编译错误:

export const f1 = function f2(action: MyAction): ThunkAction<MyAction, {}, undefined, MyAction> {
  return dispatch => {
    let advance1 = f2(action) // ts knows that advance1 is MyAction
    const result = dispatch(advance1)
    return result
  }
}

此代码有编译错误:

type F1 = (action: MyAction) => ThunkAction<MyAction, {}, undefined, MyAction>
// ts doesn't understand the type of `f2`
export const f1: F1 = function f2(action) {
  return dispatch => {
    let advance1 = f2(action)
    const result = dispatch(advance1)
    return result
  }
}

错误:

155:10 Function implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions.
    153 | type F1 = (action: MyAction) => ThunkAction<MyAction, {}, undefined, MyAction>
    154 | export const f1: F1 = function f2(action) {
  > 155 |   return dispatch => {
        |          ^
    156 |     let advance1 = f2(action)
    157 |     const result = dispatch(advance1)
    158 |     return result


157:11 'result' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.
    155 |   return dispatch => {
    156 |     let advance1 = f2(action)
  > 157 |     const result = dispatch(advance1)
        |           ^
    158 |     return result
    159 |   }
    160 | }

两个示例都具有相同的代码。区别在于在第二个示例中,我将类型提取为变量。

出什么问题了?


redux-thunk.d.ts

/**
 * A "thunk" action (a callback function that can be dispatched to the Redux
 * store.)
 *
 * Also known as the "thunk inner function", when used with the typical pattern
 * of an action creator function that returns a thunk action.
 *
 * @template TReturnType The return type of the thunk's inner function
 * @template TState The redux state
 * @template TExtraThunkARg Optional extra argument passed to the inner function
 * (if specified when setting up the Thunk middleware)
 * @template TBasicAction The (non-thunk) actions that can be dispatched.
 */
export type ThunkAction<
  TReturnType,
  TState,
  TExtraThunkArg,
  TBasicAction extends Action
> = (
  dispatch: ThunkDispatch<TState, TExtraThunkArg, TBasicAction>,
  getState: () => TState,
  extraArgument: TExtraThunkArg
) => TReturnType;

/**
 * The dispatch method as modified by React-Thunk; overloaded so that you can
 * dispatch:
 *   - standard (object) actions: `dispatch()` returns the action itself
 *   - thunk actions: `dispatch()` returns the thunk's return value
 *
 * @template TState The redux state
 * @template TExtraThunkArg The extra argument passed to the inner function of
 * thunks (if specified when setting up the Thunk middleware)
 * @template TBasicAction The (non-thunk) actions that can be dispatched.
 */
export interface ThunkDispatch<
  TState,
  TExtraThunkArg,
  TBasicAction extends Action
> {
  <TReturnType>(
    thunkAction: ThunkAction<TReturnType, TState, TExtraThunkArg, TBasicAction>
  ): TReturnType;
  <A extends TBasicAction>(action: A): A;
}

1 个答案:

答案 0 :(得分:1)

好的,这里的问题似乎在于f2()返回值中类型的循环性。 TypeScript编译器在不同的“通过”或“阶段”中执行类型推断(根据您对表达式的类型确定类型)和类型检查(根据表达式的类型确定表达式的类型)。何时发生的详细信息超出了我的范围,但是要遵循的经验法则可能是:从类型推断开始,如果编译器推断出意外/不想要的类型(或无法推断出类型),则使用类型注释或类型断言来修复它并获得类型检查,而不是类型推断。

在您的第一个示例中,

export const f1good = function f2(
  action: MyAction
): ThunkAction<MyAction, {}, undefined, MyAction> {
  return dispatch => {
    let advance1 = f2(action); 
    const result = dispatch(advance1);
    return result;
  };
};

编译器没有问题,因为函数f2()是完全注释的函数。它的返回类型ThunkAction<MyAction, {}, undefined, MyAction>由您指定。编译器很乐意检查该类型。

在第二个示例中,

type F1 = (action: MyAction) => ThunkAction<MyAction, {}, undefined, MyAction>;

export const f1bad: F1 = function f2(action) {
  return dispatch => {  // error!
    let advance1 = f2(action);
    const result = dispatch(advance1);  // error!
    return result;
  };
};

发生的事情是不同的。您已经键入了f1bad变量,但没有键入分配给它的f2函数。您依靠类型推断从给定f2的类型中给f1bad适当的类型。这种类型推断被称为“ contextual typing”,因为它从控制流迫切性“向后”运行。我的意思是,在运行代码时,先定义 first f2,然后将 then 分配给f1bad。但是您已经定义了f1bad first 的类型,并希望用它来确定f2的类型。实际上这很好,编译器通常会进行这种上下文类型化。对于您来说不幸的是,这种上下文类型输入的关键部分似乎比对f2本身的一些“时间向前”类型推断要晚一些:

f2的常规类型推断中,根据函数的实现方式推断其参数和返回类型。这就是编译器感到困惑的地方。 f2的返回类型本身就是一个返回result类型的东西的函数。当使用result参数调用时,dispatch的类型被推断为advance1的返回类型。当使用advance1参数调用时,f2参数的类型被推断为action的返回类型。哦,f2的返回类型取决于f2的返回类型。到那时,编译器会放弃,开始为事物分配any,并在您打开--noImplicitAny时警告您(应该)。

这可能被认为是错误或设计限制...我searched处理相关问题。有时将此类问题视为fixable bug,而有时将它们视为unfixable or unlikely-to-be-fixed design limitation。不确定是否存在完全匹配的问题。

无论如何,消除此错误的方法是在过程中通过注释或声明某些东西来打破类型推断的圆度。显然,您的第一个示例通过显式注释整个函数来完成此操作。以下是我在您的示例中发现的最冗长的方法:

export const f1fixed: F1 = function f2(action) {
  return dispatch => {
    let advance1 = f2(action);
    const result: MyAction = dispatch(advance1); // annotate here
    return result;
  };
};

在这里,我们已明确注释result的类型为MyAction。现在,编译器期望f2返回一个返回MyAction的函数,并且完成从F1进行的任何上下文推断就足以使一切成功。

我希望这会有所帮助。祝你好运!

Link to code