【问题标题】:How to dispatch an Action or a ThunkAction (in TypeScript, with redux-thunk)?如何调度 Action 或 ThunkAction(在 TypeScript 中,使用 redux-thunk)?
【发布时间】:2017-08-18 04:49:08
【问题描述】:

假设我有这样的代码:

import { Action, Dispatch } from 'redux';
import { ThunkAction } from 'redux-thunk';

interface StateTree {
  field: string;
}

function myFunc(action: Action | ThunkAction<void, StateTree, void>,
                dispatch: Dispatch<StateTree>) {
  dispatch(action); // <-- This is where the error comes from
}

...我从 TypeScript 编译器收到此错误:

ERROR in myFile.ts:x:y
TS2345: Argument of type 'Action | ThunkAction<void, StateTree, void>' is not assignable to parameter of type 'Action'.
  Type 'ThunkAction<void, StateTree, void>' is not assignable to type 'Action'.
  Property 'type' is missing in type 'ThunkAction<void, StateTree, void>'.

我认为问题在于 redux-thunk 类型定义文件增强了 redux Dispatch 接口的方式以及 TypeScript 无法知道要使用 Dispatch 的哪个定义。

有没有办法解决这个问题?

【问题讨论】:

    标签: typescript redux redux-thunk


    【解决方案1】:

    ThunkAction 签名随最新版本更改(现在是 ThunkAction&lt;void, Your.Store.Definition, void, AnyAction&gt;),除非有一些邪恶的双重转换(action as {} as Action),我发现更优雅的方法是将 redux 调度定义为这样的 ThunkDispatch:

    import { applyMiddleware, Store, createStore, AnyAction } from 'redux';
    import logger from 'redux-logger';
    import thunk, { ThunkDispatch } from 'redux-thunk';
    
    import { Redux } from '../definitions';
    import rootReducer from './reducers';
    import { bootstrap } from './actions';
    
    export default function configureStore() {
    
        const middleware = applyMiddleware( thunk, logger );
    
        const store: Store<Redux.Store.Definition> = createStore(rootReducer, middleware);
    
        // Here the dispatch casting:
        (store.dispatch as ThunkDispatch<Redux.Store.Definition, void, AnyAction>)( bootstrap() );
    
        return store;
    
    }
    

    如果其他人正在寻找更新的答案! ^^

    【讨论】:

      【解决方案2】:

      自从这个问题和各种答案发布以来,时间已经过去,许多事情都发生了变化。但是,我发现没有一个答案让我满意,因为前两个(michael-peyper 和 pierpytom)涉及重铸/重新定义,感觉很奇怪。第三个(joaoguerravieira)似乎更好,因为它不涉及其中任何一个,但至少对我来说,不清楚它是如何解决问题的。

      当我遇到一个我认为实际上与此相同的问题时,这似乎对我最有帮助:如何在创建的 redux 存储上获取正确类型的“调度”方法。也就是说,如何让 TypeScript 编译器同意 store.dispatch 可以调度 Actions ThunkActions。即使这与原始问题中所问的问题不完全相同(但我认为可能是),所有关于我的问题的搜索引擎查询都会让我回到这篇文章,所以我认为提出我的解决方案可能会有所帮助在这里。

      我一直发现在使用 redux 时很难找到合适的类型(也许我只是笨)所以很长一段时间我总是像这样创建我的商店:

      createStore(
          combineReducers(stuff),
          defaultState,
          applyMiddleware(thunkMiddleware));
      

      ...这总是让我处于可以在 thunk 上调用 store.dispatch 的情况,但是 TypeScript 编译器对我大喊大叫,即使它在运行时仍然可以工作。每个答案的一点点最终使我找到了我认为是解决问题的最新且无强制转换的方法。

      store 对象的 dispatch 方法的类型是由调用 redux 的 createStore 返回的内容决定的。为了在 store 的 dispatch 方法上有正确的类型,您必须在调用 applyMiddleware 时正确设置类型参数(您可以直接或最终将其作为第三个参数传递给 createStore)。 @joaoguerravieira 的回答让我朝这个方向看。为了让调度方法具有正确的类型来调度 ThunkAction 或 Action,我必须像这样调用 createStore/applyMiddleware:

      createStore(
          combineReducers(stuff),
          defaultState,
          applyMiddleware<DispatchFunctionType, StateType>(thunkMiddleware));
      

      在哪里

      type DispatchFunctionType = ThunkDispatch<StateType, undefined, AnyAction>
      

      通过这样做,我得到了这种类型的商店:

      Store<StateType, Action<StateType>> & { dispatch: DispatchFunctionType };
      

      ...这让我得到了这种类型的 store.dispatch 函数:

      Dispatch<Action<any>> & ThunkDispatch<any, undefined, AnyAction>
      

      ...它可以成功地调度一个 Action 或一个 ThunkAction 而不需要对类型大喊大叫,也不需要任何重新定义/强制转换。

      在调用 applyMiddleware 时正确设置类型参数至关重要!

      【讨论】:

      • 很好的答案。我还有另一个问题,因为我使用的是“composeWithDevTools(applyMiddleware(thunk, logger))”。所以我只是写了我自己的接口'export interface ThunkDispatchProp { dispatch: Dispatch & DispatchFunctionType }'。然后我的组件 Prop 接口没有扩展“DispatchProp”,而是“ThunkDispatchProp”。仍然感觉有点像黑客,但它有效,如果下一个人看到它,他就有机会弄清楚发生了什么。不过,我会很高兴您对此发表评论。
      • 如果你将 combineReducers 的调用移出这个调用 (const rootReducer = combineReducers(stuff)),那么你可以找到 StateType,因为它是 rootReducer 的返回类型 (type StateType = ResultType&lt;typeof rootReducer&gt;)。
      • 什么是ResultType?啊,我想你的意思是 ReturnType,好吧。
      • 据我了解,joaoguerravieira 的回答通过正确输入thunk 解决了这个问题。它被键入为ThunkMiddleware&lt;S = {}, …,因此通过将thunk 键入为ThunkMiddleware&lt;YourAppState&gt;,我们为其提供了正确的应用程序状态类型,而不仅仅是{}。然后编译器能够进行正确的类型检测。
      【解决方案3】:

      这些是正确的类型:https://github.com/reduxjs/redux-thunk/blob/master/test/typescript.ts

      最值得注意的是:

      const store = createStore(fakeReducer, applyMiddleware(thunk as ThunkMiddleware<State, Actions>));
      

      applyMiddleware 已经使用ThunkDispatch 覆盖调度。

      【讨论】:

        【解决方案4】:

        我认为您是正确的,尽管 typescript 能够处理这两种类型,但无法确定要使用哪个重载。

        我认为对您来说最好的选择是在调用 dispatch 时转换回所需的类型

        function myFunc(action: Action | ThunkAction<void, StateTree, void>, 
                        dispatch: Dispatch<StateTree>) {
          if (action instanceof ThunkAction<void, StateTree, void>) {
            dispatch(action as ThunkAction<void, StateTree, void>);
          } else {
            dispatch(action as Action);
          }
        }
        

        我希望我错了,有更好的方法来实现这一点。

        【讨论】:

          【解决方案5】:

          如果您使用 Redux Toolkit 的 configureStore() 抽象,则可以使用提供的 getDefaultMiddleware() 回调,该回调将为您提供 thunk 中间件和一个可以接受 AnyActionThunkActionstore.dispatch

          const store = configureStore({
            devTools: process.env.NODE_ENV !== 'production',
            // getDefaultMiddleware() adds redux-thunk by default
            // It also provides a properly typed store.dispatch that can accept AnyAction and ThunkAction
            // See https://redux-toolkit.js.org/api/getDefaultMiddleware
            middleware: (getDefaultMiddleware) =>
              getDefaultMiddleware().concat(anyOtherMiddleware),
            preloadedState: {},
            reducer: reducers,
          });
          

          您可以在他们的文档中阅读有关 configureStore()getDefaultMiddleware() 的更多信息。

          【讨论】:

            猜你喜欢
            • 2021-02-27
            • 2021-01-01
            • 2020-03-17
            • 2019-01-24
            • 2016-12-11
            • 2021-02-11
            • 2019-10-23
            • 2020-08-17
            • 2021-08-24
            相关资源
            最近更新 更多