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

Posted

技术标签:

【中文标题】如何在 Typescript 中使用 redux-thunk 使用 ThunkAction 正确键入 thunk?【英文标题】:How to properly type a thunk with ThunkAction using redux-thunk in Typescript? 【发布时间】:2021-01-01 00:05:38 【问题描述】:

我正在尝试使用 Typescript 输入检查我的 redux-thunk 代码。

从 Redux 的官方文档:Usage with Redux Thunk,我们得到这个例子:

// src/thunks.ts

import  Action  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, Action<string>> => async dispatch => 
  const asyncResp = await exampleAPI()
  dispatch(
    sendMessage(
      message,
      user: asyncResp,
      timestamp: new Date().getTime()
    )
  )


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

为减少重复,您可能希望在商店文件中定义一次可重用的 AppThunk 类型,然后在编写 thunk 时使用该类型:

export type AppThunk<ReturnType = void> = ThunkAction<
  ReturnType,
  RootState,
  unknown,
  Action<string>
>

问题

我没有完全理解 ThunkAction 类型的用法:

ThunkAction<void, RootState, unknown, Action<string>>

有 4 个类型参数,对吧?

第一 - void

这是 thunk 的返回类型,对吧?不应该是Promise&lt;void&gt;,因为它是async

第二次 - RootState

这是完整的状态形状,对吗?我的意思是,它不是切片,而是完整的状态。

第三 - unknown

为什么是unknown?这是什么?

第 4 次 - Action&lt;string&gt;

这个也不明白。为什么Action&lt;T&gt; 将字符串作为参数?它应该总是string 吗?为什么会这样?

【问题讨论】:

【参考方案1】:

其实大部分答案都可以在source code找到。

 * @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;

1st - void

thunk 的返回类型是 NOT Promise。它只是一个返回 inner 函数的函数。由于 inner 函数不返回任何内容,因此返回类型为void

第二次 - RootState

是的,这是正确的。参考上面的源代码。具体来说,这行* @template TState The redux state

第三 - unknown

你读到了未知的here。

第 4 次 - Action&lt;string&gt;

参考这行* @template TBasicAction The (non-thunk) actions that can be dispatched.

 * @template T the type of the action's `type` tag.
 */
export interface Action<T = any> 
  type: T

从here中提取代码sn-p。

Action&lt;T&gt; 接受 string 类型,因为操作类型 ISstring,而 ThunkAction 类型需要 Action 类型,如这里所示 - TBasicAction extends Action

更正

第一个 - void

我误解并忘记了async 函数。请参阅下面的第一条评论。

【讨论】:

我认为对第 1 点的解释不太正确。默认情况下,异步函数确实返回承诺 - 您可以在此示例中看到 TS 推断:typescriptlang.org/play?#code/… 感谢您的回答。关于3rd 项目的最后一件事。我不是在问unknown 类型本身是什么,而是我为什么在第三个参数中键入unkown?我已经深入研究了源代码,我认为这是额外参数的类型,例如,当您将 thunk 中间件初始化为 applyMiddleware(thunk.withExtraArgument(api)), 时。所以你会在你的 thunk 的“创造者”中得到return (dispatch, getState, api)。这是正确的,您可以将其添加到您的答案中。再次感谢。 @cbdeveloper - 是的,你在这两种情况下都是对的。它适用于 thunk - 不是 thunk 创建者,也不是必须异步的。 Promise 在这里是一种更准确的类型 - 但实际上它只是额外的不必要的复杂性。除非你的代码的另一部分要求函数的结果更具体(例如,如果 redux-thunk 内部关心函数是一个承诺这一事实)——那么就不需要添加额外的信息。 @mbdavis 你是正确的。我已经完全忘记了redux-thunk 中的async 函数。感谢您指出;) @cbdeveloper unknown 用于此 API 以表示“这可以是任何值,因此您必须在使用前执行某种类型的检查”。这迫使用户安全地反省返回值。【参考方案2】:

来自https://github.com/reduxjs/redux-thunk/blob/d28ab03fd1d2dd1de402928a9589857e97142e09/src/index.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;

First - TReturnType

这里我们不关心返回类型 - 反正我们不等待 thunk 的结果。在 TS 中,可以将任何函数分配给 void 签名,因为它不会损害类型安全。 Here's 一个示例,表明我可以将各种异步/非异步函数分配给 void 函数签名:

我们使用提供的调度函数来触发我们关心的下一个动作 - 所以就时间而言唯一重要的是我们在等待某些事情的异步函数内部。 Redux-thunk 在内部没有对这个函数的结果做任何事情。以下是关于 thunk 如何在后台工作的精彩解释(只需预览):

https://frontendmasters.com/courses/rethinking-async-js/synchronous-and-asynchronous-thunks/

第二 - TState 是的 - 这是整个商店状态类型

第三 - TExtraThunkARg 您可以添加自己的自定义额外参数,该参数在分派和 getState 之后传递到 thunk。这默认为未知,因为除非您明确提供它,否则不清楚它将是什么。这是类型安全的,因为尝试与未知参数交互会导致编译时错误。

更多:https://github.com/reduxjs/redux-thunk#injecting-a-custom-argument

第四 - TBasicAction

这是一个动作类型。动作是最基本的动作类型——每个type 属性都是一个纯字符串。或者,您可以提供自己的更具体的类型 - 即 type MyActionType = 'FOO_ACTION' | 'BAR_ACTION' 以进一步安全/缩小类型。然后,您可以将其用作操作。

【讨论】:

以上是关于如何在 Typescript 中使用 redux-thunk 使用 ThunkAction 正确键入 thunk?的主要内容,如果未能解决你的问题,请参考以下文章

如何将 Redux 4.0 TypeScript 绑定与 redux-thunk 一起使用

如何使用 redux-thunk 和 TypeScript 调度 ThunkAction

无法在 Typescript 中调度 redux 操作

反应导航:如何在打字稿中使用 NavigationStackScreenComponent 类型传递 redux 道具

如何使用 TypeScript 为 Redux-Observable 的 Epic 输入 Actions

如何使用 typescript、next-redux-wrapper、getServerSideProps?