如何在打字稿中使用中间件键入 redux thunk

Posted

技术标签:

【中文标题】如何在打字稿中使用中间件键入 redux thunk【英文标题】:How to type redux thunk with middleware in typescript 【发布时间】:2021-06-29 09:35:20 【问题描述】:

所以我有一个 SPFx webpart(使用打字稿)并添加了 Redux。现在一切正常,但是需要键入 thunk 函数,我不知道如何键入它们。

所以这是我要调度的操作:

  export const setListByMiddelware:any = (listId:string) => (dispatch, getState) => 
    dispatch(listIdAdded(listId));
    dispatch(spApiCallBegan(
        method: GET_LIST_ITEMS,
        options: 
            listId
        ,
        onSuccess: itemsAdded.type
    ));
  

这是执行 SP 调用的 SharePoint 中间件:

import SP from '../../services/SharePointService';
import * as actions from '../SP_API';

const api = store => next => async action => 
    if(action.type !== actions.spApiCallBegan.type) 
        next(action);
        return;
    

    const  method, options, onSuccess, onError, onStart  = action.payload;

    if(onStart)  
        store.dispatch( type: onStart );
    

    next(action);

    try 
        const result = await SP[method](options);

        store.dispatch(actions.spApiCallSuccess(result));
        if(onSuccess) 
            store.dispatch(
                type: onSuccess,
                payload: result
            );
        
     catch(ex) 
        store.dispatch(actions.spApiCallFailed(ex.message));
        if(onError) 
            store.dispatch( 
                type: onError,  
                payload: ex.message 
            );
        
    


export default api;

现在你可以看到我通过简单地将 thunk 输入到any 解决了这个问题,并且效果很好,但我想强烈输入它,因为这些是我的组件的功能实际使用。

这是我正在使用的堆栈: redux@4.0.5 react-redux@7.2.3 @reduxjs/toolkit@1.5.1

我在输入 Redux 时遇到了真正的麻烦,正如你所看到的,我或多或少地放弃了这一点,而谷歌在这方面并没有多大帮助。因此,我的 Redux 代码大部分是无类型的。

至于打字稿,我想我使用的是 TS 3.3 自带的 SPFx 1.11.0,我使用的是 SPFx 版本。

编辑 正如 markerikson 所指出的那样,我已经阅读了有关在此处输入 Redux 的官方 Redux 文档: https://redux.js.org/recipes/usage-with-typescript#type-checking-middleware

但是当我实现它时,我得到:Type '(listId: string) => (dispatch: any, getState: any) => void' is notassignable to type 'ThunkAction'。 参数“listId”和“dispatch”的类型不兼容。 类型 'ThunkDispatch' 不可分配给类型 'string'。 对于 thunk'api' 在其自己的类型注释中直接或间接引用. 用于中间件。

这是我正在使用的商店设置:

import  configureStore, getDefaultMiddleware  from '@reduxjs/toolkit';
import listRedurcer,  IListSate from './lists';
import SP_API from './middleware/SP_API';


const store = configureStore(
    reducer: listRedurcer,
    middleware: [
        ...getDefaultMiddleware(),
        SP_API
    ],
    devTools : 
        name: 'ReduxList'
    
);

// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof store.getState>;
// Inferred type: posts: PostsState, comments: CommentsState, users: UsersState
export type AppDispatch = typeof store.dispatch;

export default store;

对于 thunk,我也使用了这个 ThunkAction&lt;void, RootState, string, AnyAction&gt;,但我得到了同样的错误。

【问题讨论】:

【参考方案1】:

Redux docs "Usage with TypeScript" page specifically covers typing middleware and thunks:

自定义中间件如下所示:

import  Middleware  from 'redux'

import  RootState  from '../store'

export const exampleMiddleware: Middleware<
  , // Most middleware do not modify the dispatch return value
  RootState
> = storeApi => next => action => 
  const state = storeApi.getState() // correctly typed as RootState

对于重击:

import  AnyAction  from 'redux'
import  sendMessage  from './store/chat/actions'
import  RootState  from './store'
import  ThunkAction  from 'redux-thunk'

export const thunkSendMessage = (
  message: string
): ThunkAction<void, RootState, unknown, AnyAction> => async dispatch => 
  const asyncResp = await exampleAPI()
  dispatch(
    sendMessage(
      message,
      user: asyncResp,
      timestamp: new Date().getTime()
    )
  )


function exampleAPI() 
  return Promise.resolve('Async Chat Bot')

更新

听起来你想像这样使用ThunkAction

export const setListByMiddelware : ThunkAction = (listId:string) => (dispatch, getState) => 

这是不正确的。 setListByMiddelware 不是 ThunkAction - 它返回 ThunkAction。正确的用法是:

export const setListByMiddelware = (listId:string) : ThunkAction => (dispatch, getState) => 

另外,你的 middleware 参数是错误的:

middleware 参数应该是一个接受 getDefaultMiddleware 作为其参数的回调 然后您应该调用它,并使用数组的 prependconcat 方法来设置正确的类型:
const store = configureStore(
    reducer: listRedurcer,
    middleware: getDefaultMiddleware => getDefaultMiddleware().concat(SP_API),
    devTools : 
        name: 'ReduxList'
    
);

见https://redux-toolkit.js.org/usage/usage-with-typescript#correct-typings-for-the-dispatch-type

【讨论】:

所以我确实阅读了文档,也许我应该澄清一下。无论如何,当我使用 ThunkAction&lt;void, RootState, unknown, AnyAction&gt; 类型时,我会收到以下错误 Type '(listId: string) => (dispatch: any, getState: any) => void' is not assignable to type 'ThunkAction'。参数“listId”和“dispatch”的类型不兼容。类型 'ThunkDispatch' 不能分配给类型 'string'。 对不起,我只是不明白,我什至使用了 ThunkAction&lt;void, RootState, string, AnyAction&gt; 显示你在哪里使用ThunkAction类型? 是的,我尝试使用它,但我想我使用的是错误的,当我将它应用到setListByMiddelware thunk 时,我只是得到了上面的错误。 问题似乎是他们不能使用RootState 来键入中间件,同时还从store 派生RootState 类型,因为这是循环的(因为store 取决于中间件)。我建议明确输入RootState。你有更好的主意吗? 是的,在这种情况下,解决方法是编写一个单独的rootReducer 函数,然后执行type RootState = ReturnType&lt;typeof rootReducer&gt;,而不是来自store.getState【参考方案2】:

ThunkAction&lt;void, RootState, string, AnyAction&gt; 如果你一次性创建了你的 thunk 是合适的,比如setListByMiddelware=(dispatch, getState, listId:string)=&gt;,但setListByMiddelware 本身不是一个 thunk,它更像是一个 thunk 工厂,所以你需要它的类型来反映它,试试

const setListByMiddelware:(listId:string)=>ThunkAction<void, RootState, never, AnyAction> = 
  (listId:string) => (dispatch, getState) => 

【讨论】:

当我尝试您的解决方案时,我得到了 “ThunkAction”类型的参数不能分配给“AnyAction”类型的参数。 “ThunkAction”类型中缺少属性“类型”,但在“AnyAction”类型中是必需的。 看起来dispatch输入不正确,试试dispatch:Dispatch 同样的问题,这是我尝试过的实现:export const setListByMiddelware:(listId:string)=&gt;ThunkAction&lt;void, RootState, never, AnyAction&gt; = (listId:string) =&gt; (dispatch:Dispatch, getState) =&gt; /* thunk code */ 和错误一模一样?..试试dispatch:ThunkDispatch&lt;RootState,never, AnyAction&gt;也许? 是的,有效,但很抱歉我不知道接受谁的答案,因为我从所有提交的答案中得到了答案【参考方案3】:

'api'在自己的类型注解中被直接或间接引用。

您收到此错误是因为中间件类型取决于派生自storeRootState 类型。但是store 类型取决于中间件,所以你有一个循环类型。

解决此问题的一种方法是定义RootState 的类型,而不使用store。在你的情况下,你的整个减速器是listReducer,所以你可以这样做:

export type RootState = ReturnType<typeof listReducer>;

这将适用于@markerikson 建议的api 类型

const api: Middleware<, RootState> = store => next => async action => 

setListByMiddleware 是一个返回类型ThunkAction 的函数。函数本身不是ThunkAction

export const setListByMiddelware = (
  listId: string
): ThunkAction<void, RootState, never, AnyAction> => (dispatch, getState) => 
  dispatch(listIdAdded(listId));
  dispatch(
    spApiCallBegan(
      method: GET_LIST_ITEMS,
      options: 
        listId
      ,
      onSuccess: itemsAdded.type
    )
  );
;

【讨论】:

以上是关于如何在打字稿中使用中间件键入 redux thunk的主要内容,如果未能解决你的问题,请参考以下文章

如何在打字稿中键入枚举变量?

在打字稿中键入注释[重复]

如何在 Typescript 中使用 redux-thunk 使用 ThunkAction 正确键入 thunk?

在打字稿中以自引用方式键入对象

在打字稿中的基于 Vue 类的组件中键入 prop

打字稿中的“三个点”是啥意思?