TypeScript: how to correctly type React Redux APIs">TypeScript: how to correctly type React Redux APIs">
跳至主要内容

TypeScript 的使用

从 React-Redux v8 开始,React-Redux 完全用 TypeScript 编写,类型包含在发布的包中。这些类型还导出了一些辅助函数,使编写 Redux store 和 React 组件之间类型安全的接口变得更加容易。

信息

最近更新的 @types/react@18 主要版本更改了组件定义,默认情况下不再包含 children 作为 prop。如果您在项目中有多个 @types/react 副本,这会导致错误。要解决此问题,请告诉您的包管理器将 @types/react 解析为单个版本。详情

https://github.com/facebook/react/issues/24304#issuecomment-1094565891

使用 TypeScript 的标准 Redux Toolkit 项目设置

我们假设一个典型的 Redux 项目同时使用 Redux Toolkit 和 React Redux。

Redux Toolkit (RTK) 是编写现代 Redux 逻辑的标准方法。RTK 已经用 TypeScript 编写,其 API 旨在为 TypeScript 使用提供良好的体验。

用于 Create-React-App 的 Redux+TS 模板 附带了已配置的这些模式的工作示例。

定义根状态和调度类型

使用 configureStore 不需要任何额外的类型。但是,您需要提取 RootState 类型和 Dispatch 类型,以便在需要时可以引用它们。从存储本身推断这些类型意味着它们会在您添加更多状态切片或修改中间件设置时正确更新。

由于这些是类型,因此可以安全地将它们直接从您的存储设置文件(例如 app/store.ts)导出,并直接导入到其他文件中。

app/store.ts
import { configureStore } from '@reduxjs/toolkit'
// ...

const store = configureStore({
reducer: {
posts: postsReducer,
comments: commentsReducer,
users: usersReducer,
},
})

// 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

定义类型化钩子

虽然可以将 RootStateAppDispatch 类型导入到每个组件中,但最好为您的应用程序中的使用创建 useDispatchuseSelector 钩子的预类型化版本。这对于以下几个原因很重要

  • 对于 useSelector,它可以省去您每次都键入 (state: RootState) 的麻烦
  • 对于 useDispatch,默认的 Dispatch 类型不知道 thunk 或其他中间件。为了正确调度 thunk,您需要使用来自存储的包含 thunk 中间件类型的特定自定义 AppDispatch 类型,并将其与 useDispatch 一起使用。添加预类型化的 useDispatch 钩子可以防止您忘记在需要的地方导入 AppDispatch

由于这些是实际的变量,而不是类型,因此在单独的文件(例如 app/hooks.ts)中定义它们很重要,而不是存储设置文件。这使您可以将它们导入到需要使用这些钩子的任何组件文件中,并避免潜在的循环导入依赖关系问题。

.withTypes()

以前,使用您的应用程序设置“预类型化”钩子的方法略有不同。结果将类似于下面的代码片段

app/hooks.ts
import type { TypedUseSelectorHook } from 'react-redux'
import { useDispatch, useSelector, useStore } from 'react-redux'
import type { AppDispatch, AppStore, RootState } from './store'

// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch: () => AppDispatch = useDispatch
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
export const useAppStore: () => AppStore = useStore

React Redux v9.1.0 为这些钩子中的每一个添加了一个新的 .withTypes 方法,类似于 Redux Toolkit 的 createAsyncThunk 上的 .withTypes 方法。

现在的设置变为

app/hooks.ts
import { useDispatch, useSelector, useStore } from 'react-redux'
import type { AppDispatch, AppStore, RootState } from './store'

// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = useDispatch.withTypes<AppDispatch>()
export const useAppSelector = useSelector.withTypes<RootState>()
export const useAppStore = useStore.withTypes<AppStore>()

手动类型化钩子

我们建议使用上面显示的预类型化 useAppSelectoruseAppDispatch 钩子。如果你不喜欢使用它们,这里是如何自己类型化钩子。

类型化 useSelector 钩子

在为 useSelector 编写选择器函数时,你应该明确定义 state 参数的类型。TS 应该能够推断出选择器的返回类型,该类型将被用作 useSelector 钩子的返回类型。

interface RootState {
isOn: boolean
}

// TS infers type: (state: RootState) => boolean
const selectIsOn = (state: RootState) => state.isOn

// TS infers `isOn` is boolean
const isOn = useSelector(selectIsOn)

这也可以在内联中完成

const isOn = useSelector((state: RootState) => state.isOn)

类型化 useDispatch 钩子

默认情况下,useDispatch 的返回值是 Redux 核心类型定义的标准 Dispatch 类型,因此不需要声明。

const dispatch = useDispatch()

如果你有 Dispatch 类型的自定义版本,你可以显式地使用该类型。

// store.ts
export type AppDispatch = typeof store.dispatch

// MyComponent.tsx
const dispatch: AppDispatch = useDispatch()

类型化 connect 高阶组件

自动推断连接的道具

connect 包含两个按顺序调用的函数。第一个函数接受 mapStatemapDispatch 作为参数,并返回第二个函数。第二个函数接受要包装的组件,并返回一个新的包装组件,该组件从 mapStatemapDispatch 传递下来道具。通常,这两个函数一起调用,例如 connect(mapState, mapDispatch)(MyComponent)

该包包含一个辅助类型 ConnectedProps,它可以从第一个函数中提取 mapStateToPropsmapDispatchToProps 的返回类型。这意味着,如果你将 connect 调用拆分为两个步骤,所有“来自 Redux 的道具”都可以自动推断,而无需手动编写。虽然这种方法可能在你使用 React-Redux 一段时间后感觉不寻常,但它确实简化了类型声明。

import { connect, ConnectedProps } from 'react-redux'

interface RootState {
isOn: boolean
}

const mapState = (state: RootState) => ({
isOn: state.isOn,
})

const mapDispatch = {
toggleOn: () => ({ type: 'TOGGLE_IS_ON' }),
}

const connector = connect(mapState, mapDispatch)

// The inferred type will look like:
// {isOn: boolean, toggleOn: () => void}
type PropsFromRedux = ConnectedProps<typeof connector>

ConnectedProps 的返回值类型可用于为您的 props 对象进行类型化。

interface Props extends PropsFromRedux {
backgroundColor: string
}

const MyComponent = (props: Props) => (
<div style={{ backgroundColor: props.backgroundColor }}>
<button onClick={props.toggleOn}>
Toggle is {props.isOn ? 'ON' : 'OFF'}
</button>
</div>
)

export default connector(MyComponent)

由于类型可以在任何顺序定义,因此您仍然可以在声明连接器之前声明组件,如果您需要的话。

// alternately, declare `type Props = PropsFromRedux & {backgroundColor: string}`
interface Props extends PropsFromRedux {
backgroundColor: string;
}

const MyComponent = (props: Props) => /* same as above */

const connector = connect(/* same as above*/)

type PropsFromRedux = ConnectedProps<typeof connector>

export default connector(MyComponent)

手动类型化 connect

connect 高阶组件的类型化比较复杂,因为它有 3 个 props 源:mapStateToPropsmapDispatchToProps 和从父组件传递的 props。以下是一个手动进行类型化的完整示例。

import { connect } from 'react-redux'

interface StateProps {
isOn: boolean
}

interface DispatchProps {
toggleOn: () => void
}

interface OwnProps {
backgroundColor: string
}

type Props = StateProps & DispatchProps & OwnProps

const mapState = (state: RootState) => ({
isOn: state.isOn,
})

const mapDispatch = {
toggleOn: () => ({ type: 'TOGGLE_IS_ON' }),
}

const MyComponent = (props: Props) => (
<div style={{ backgroundColor: props.backgroundColor }}>
<button onClick={props.toggleOn}>
Toggle is {props.isOn ? 'ON' : 'OFF'}
</button>
</div>
)

// Typical usage: `connect` is called after the component is defined
export default connect<StateProps, DispatchProps, OwnProps>(
mapState,
mapDispatch,
)(MyComponent)

也可以通过推断 mapStatemapDispatch 的类型来缩短代码。

const mapState = (state: RootState) => ({
isOn: state.isOn,
})

const mapDispatch = {
toggleOn: () => ({ type: 'TOGGLE_IS_ON' }),
}

type StateProps = ReturnType<typeof mapState>
type DispatchProps = typeof mapDispatch

type Props = StateProps & DispatchProps & OwnProps

但是,如果 mapDispatch 被定义为一个对象并且也引用了 thunk,则以这种方式推断 mapDispatch 的类型将失效。

建议

钩子 API 通常在使用静态类型时更简单。如果您正在寻找使用静态类型与 React-Redux 的最简单解决方案,请使用钩子 API。

如果您使用的是 connect我们建议使用 ConnectedProps<T> 方法来推断来自 Redux 的 props,因为这需要最少的显式类型声明。

资源

有关更多信息,请参阅以下资源