与 TypeScript 一起使用
¥Usage with TypeScript
从 React-Redux v8 开始,React-Redux 完全用 TypeScript 编写,并且类型包含在已发布的包中。这些类型还导出一些辅助程序,以便更轻松地在 Redux 存储和 React 组件之间编写类型安全的接口。
¥As of React-Redux v8, React-Redux is fully written in TypeScript, and the types are included in the published package. The types also export some helpers to make it easier to write typesafe interfaces between your Redux store and your React components.
最近更新的 @types/react@18
主要版本更改了组件定义,默认删除了 children
作为 prop。如果你的项目中有多个 @types/react
副本,这会导致错误。要解决此问题,请告诉你的包管理器将 @types/react
解析为单个版本。细节:
¥The recently updated @types/react@18
major version has changed component definitions to remove having children
as a prop by default. This causes errors if you have multiple copies of @types/react
in your project. To fix this, tell your package manager to resolve @types/react
to a single version. Details:
https://github.com/facebook/react/issues/24304#issuecomment-1094565891
使用 TypeScript 设置标准 Redux 工具包项目
¥Standard Redux Toolkit Project Setup with TypeScript
我们假设一个典型的 Redux 项目同时使用 Redux Toolkit 和 React Redux。
¥We assume that a typical Redux project is using Redux Toolkit and React Redux together.
Redux 工具包 (RTK) 是编写现代 Redux 逻辑的标准方法。RTK 已经用 TypeScript 编写,其 API 旨在为 TypeScript 使用提供良好的体验。
¥Redux Toolkit (RTK) is the standard approach for writing modern Redux logic. RTK is already written in TypeScript, and its API is designed to provide a good experience for TypeScript usage.
Create-React-App 的 Redux+TS 模板 附带了已配置的这些模式的工作示例。
¥The Redux+TS template for Create-React-App comes with a working example of these patterns already configured.
定义根状态和调度类型
¥Define Root State and Dispatch Types
使用 configureStore 不需要任何额外的输入。但是,你将需要提取 RootState
类型和 Dispatch
类型,以便可以根据需要引用它们。从存储本身推断这些类型意味着当你添加更多状态片或修改中间件设置时它们会正确更新。
¥Using configureStore should not need any additional typings. You will, however, want to extract the RootState
type and the Dispatch
type so that they can be referenced as needed. Inferring these types from the store itself means that they correctly update as you add more state slices or modify middleware settings.
由于这些是类型,因此可以安全地直接从存储设置文件(例如 app/store.ts
)导出它们并将它们直接导入到其他文件中。
¥Since those are types, it's safe to export them directly from your store setup file such as app/store.ts
and import them directly into other files.
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
定义类型化钩子
¥Define Typed Hooks
虽然可以将 RootState
和 AppDispatch
类型导入到每个组件中,但最好创建 useDispatch
和 useSelector
钩子的预键入版本以供在应用中使用。这很重要,原因如下:
¥While it's possible to import the RootState
and AppDispatch
types into each component, it's better to create pre-typed versions of the useDispatch
and useSelector
hooks for usage in your application. This is important for a couple reasons:
对于
useSelector
,你无需每次都输入(state: RootState)
¥For
useSelector
, it saves you the need to type(state: RootState)
every time对于
useDispatch
,默认的Dispatch
类型不知道 thunk 或其他中间件。为了正确分派 thunk,你需要使用存储中包含 thunk 中间件类型的特定自定义AppDispatch
类型,并将其与useDispatch
一起使用。添加预先输入的useDispatch
钩子可以防止你忘记在需要的地方导入AppDispatch
。¥For
useDispatch
, the defaultDispatch
type does not know about thunks or other middleware. In order to correctly dispatch thunks, you need to use the specific customizedAppDispatch
type from the store that includes the thunk middleware types, and use that withuseDispatch
. Adding a pre-typeduseDispatch
hook keeps you from forgetting to importAppDispatch
where it's needed.
由于这些是实际变量,而不是类型,因此在单独的文件(例如 app/hooks.ts
)中定义它们非常重要,而不是在存储设置文件中。这允许你将它们导入到任何需要使用钩子的组件文件中,并避免潜在的循环导入依赖问题。
¥Since these are actual variables, not types, it's important to define them in a separate file such as app/hooks.ts
, not the store setup file. This allows you to import them into any component file that needs to use the hooks, and avoids potential circular import dependency issues.
.withTypes()
以前,"pre-typing" 钩子与应用设置的方法略有不同。结果类似于下面的代码片段:
¥Previously, the approach for "pre-typing" hooks with your app setting was a little varied. The result would look something like the snippet below:
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
方法。
¥React Redux v9.1.0 adds a new .withTypes
method to each of these hooks, analogous to the .withTypes
method found on Redux Toolkit's createAsyncThunk
.
现在的设置变成:
¥The setup now becomes:
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>()
手动键入 Hook
¥Typing Hooks Manually
我们建议使用上面所示的预先键入的 useAppSelector
和 useAppDispatch
钩子。如果你不想使用它们,请参阅以下如何自行键入钩子。
¥We recommend using the pre-typed useAppSelector
and useAppDispatch
hooks shown above. If you prefer not to use those, here is how to type the hooks by themselves.
键入 useSelector
钩子
¥Typing the useSelector
hook
在编写与 useSelector
一起使用的选择器函数时,应显式定义 state
参数的类型。然后 TS 应该能够推断出选择器的返回类型,该返回类型将被重用为 useSelector
钩子的返回类型:
¥When writing selector functions for use with useSelector
, you should explicitly define the type of the state
parameter. TS should be able to then infer the return type of the selector, which will be reused as the return type of the useSelector
hook:
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)
这也可以内联完成:
¥This can also be done inline as well:
const isOn = useSelector((state: RootState) => state.isOn)
键入 useDispatch
钩子
¥Typing the useDispatch
hook
默认情况下,useDispatch
的返回值是 Redux 核心类型定义的标准 Dispatch
类型,因此不需要声明:
¥By default, the return value of useDispatch
is the standard Dispatch
type defined by the Redux core types, so no declarations are needed:
const dispatch = useDispatch()
如果你有 Dispatch
类型的自定义版本,则可以显式使用该类型:
¥If you have a customized version of the Dispatch
type, you may use that type explicitly:
// store.ts
export type AppDispatch = typeof store.dispatch
// MyComponent.tsx
const dispatch: AppDispatch = useDispatch()
输入 connect
高阶组件
¥Typing the connect
higher order component
自动推断连接的 props
¥Inferring The Connected Props Automatically
connect
由两个按顺序调用的函数组成。第一个函数接受 mapState
和 mapDispatch
作为参数,并返回第二个函数。第二个函数接受要封装的组件,并返回一个新的封装组件,该组件从 mapState
和 mapDispatch
传递 props。通常,两个函数一起调用,如 connect(mapState, mapDispatch)(MyComponent)
。
¥connect
consists of two functions that are called sequentially. The first function accepts mapState
and mapDispatch
as arguments, and returns a second function. The second function accepts the component to be wrapped, and returns a new wrapper component that passes down the props from mapState
and mapDispatch
. Normally, both functions are called together, like connect(mapState, mapDispatch)(MyComponent)
.
该包包含一个辅助类型 ConnectedProps
,它可以从第一个函数中提取 mapStateToProps
和 mapDispatchToProps
的返回类型。这意味着,如果你将 connect
调用分为两个步骤,则可以自动推断出所有 "来自 Redux 的属性",而无需手动编写它们。如果你已经使用 React-Redux 一段时间,这种方法可能会感觉不寻常,但它确实大大简化了类型声明。
¥The package includes a helper type, ConnectedProps
, that can extract the return types of mapStateToProps
and mapDispatchToProps
from the first function. This means that if you split the connect
call into two steps, all of the "props from Redux" can be inferred automatically without having to write them by hand. While this approach may feel unusual if you've been using React-Redux for a while, it does simplify the type declarations considerably.
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 对象。
¥The return type of ConnectedProps
can then be used to type your props object.
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)
由于类型可以按任何顺序定义,因此如果需要,你仍然可以在声明连接器之前声明组件。
¥Because types can be defined in any order, you can still declare your component before declaring the connector if you want.
// 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
¥Manually Typing connect
connect
高阶组件的输入有些复杂,因为 props 有 3 个来源:mapStateToProps
、mapDispatchToProps
和从父组件传入的 props。这是手动执行此操作的完整示例。
¥The connect
higher-order component is somewhat complex to type, because there are 3 sources of props: mapStateToProps
, mapDispatchToProps
, and props passed in from the parent component. Here's a full example of what it looks like to do that manually.
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)
还可以通过推断 mapState
和 mapDispatch
的类型来稍微缩短这个时间:
¥It is also possible to shorten this somewhat, by inferring the types of mapState
and mapDispatch
:
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
的类型将会中断。
¥However, inferring the type of mapDispatch
this way will break if it is defined as an object and also refers to thunks.
建议
¥Recommendations
Hooks API 通常更易于与静态类型一起使用。如果你正在寻找在 React-Redux 中使用静态类型的最简单解决方案,请使用 hooks API。
¥The hooks API is generally simpler to use with static types. If you're looking for the easiest solution for using static types with React-Redux, use the hooks API.
如果你使用 connect
,我们建议使用 ConnectedProps<T>
方法从 Redux 推断 props,因为这需要最少的显式类型声明。
¥If you're using connect
, we recommend using the ConnectedProps<T>
approach for inferring the props from Redux, as that requires the fewest explicit type declarations.
资源
¥Resources
有关更多信息,请参阅以下附加资源:
¥For additional information, see these additional resources:
Redux 文档:与 TypeScript 一起使用:如何使用 Redux Toolkit、Redux 核心以及 React Redux 和 TypeScript 的示例
¥Redux docs: Usage with TypeScript: Examples of how to use Redux Toolkit, the Redux core, and React Redux with TypeScript
Redux 工具包文档:TypeScript 快速入门:展示如何将 RTK 和 React-Redux hooks API 与 TypeScript 结合使用
¥Redux Toolkit docs: TypeScript Quick start: shows how to use RTK and the React-Redux hooks API with TypeScript
React+TypeScript 备忘单:将 React 与 TypeScript 结合使用的综合指南
¥React+TypeScript Cheatsheet: a comprehensive guide to using React with TypeScript
TypeScript 指南中的 React + Redux:有关将 React 和 Redux 与 TypeScript 结合使用的模式的大量信息
¥React + Redux in TypeScript Guide: extensive information on patterns for using React and Redux with TypeScript