跳到主要内容

Redux Toolkit TypeScript 快速开始

你将学到什么
  • 如何通过 TypeScript 设置和使用 Redux Toolkit 和 React-Redux
预置知识

介绍

欢迎来到 Redux Toolkit TypeScript 快速入门教程! 本教程将简要展示如何将 TypeScript 与 Redux Toolkit 一起使用

本页重点介绍如何设置 TypeScript 相关的方面。 了解 Redux 是什么、它是如何工作的,以及如何使用 Redux Toolkit 的完整示例, 请参阅“教程索引”页面中链接的教程。

Redux Toolkit 已经用 TypeScript 编写,所以它的 TS 类型定义是内置的。

React Redux 在 NPM 上有一个单独的 @types/react-redux 类型定义包有它的类型定义。除了引入类型库函数之外,这些类型还导出了一些帮助器,以便更轻松地在 Redux 存储 store 和 React 组件之间编写安全的类型接口。

从 React Redux v7.2.3, react-redux 包依赖于@types/react-redux, 因此类型定义将与库一起自动安装。 除非,你需要自己手动安装它们(通常是 npm install @types/react-redux )。

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

启动项目

定义根 State 和 Dispatch 类型

Redux Toolkit's configureStore API 不需要任何额外的类型。 但是,你需要提取 RootState 类型和 Dispatch 类型。以便可以根据需要引用它们。从 store 本身推断这些类型,意味着它们会随着你添加更多 state slices 或修改 middleware 设置而正确更新。

因为有了这些类型定义,可以安全地直接从你的 store 设置文件(例如 app/store.ts)导出它们,然后将它们直接导入其他文件。

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

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

// 从 store 本身推断出 `RootState` 和 `AppDispatch` 类型
export type RootState = ReturnType<typeof store.getState>
// 推断出类型: {posts: PostsState, comments: CommentsState, users: UsersState}
export type AppDispatch = typeof store.dispatch

定义 Hooks 类型

尽管你可以将 RootStateAppDispatch 类型导入每个组件, 更好的方式是创建 useDispatchuseSelector 钩子的类型定义,以便在你的应用程序中使用 有几个重要的原因:

  • 对于 useSelector ,它不需要你每次输入(state: RootState)
  • 对于 useDispatch ,默认的 Dispatch 类型不知道 thunk 。为了正确调度 thunk ,你需要使用 store 中包含 thunk 中间件类型的特定自定义 AppDispatch 类型,并将其与 useDispatch 一起使用。添加一个预先输入的 useDispatch 钩子可以防止你忘记在需要的地方导入 AppDispatch

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

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

// 在整个应用程序中使用,而不是简单的 `useDispatch` 和 `useSelector`
export const useAppDispatch: () => AppDispatch = useDispatch
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector

应用程序中使用

定义 slice state 和 action 类型

每个 slice 文件都应该为其初始 state 定义一个类型,以便createSlice 可以在每种情况下正确推断state 的类型 reducer。

所有生成的动作都应该使用 Redux Toolkit 中的 PayloadAction<T> 类型定义,该类型将 action.payload 字段的类型作为其通用参数。

你可以从此处的 store 文件中安全地导入 RootState 类型。这是一个循环导入,但 TypeScript 编译器可以正确处理类型。 这对于编写选择器函数等用例可能是必需的。

features/counter/counterSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
import type { RootState } from '../../app/store'

// 为 slice state 定义一个类型
interface CounterState {
value: number
}

// 使用该类型定义初始 state
const initialState: CounterState = {
value: 0
}

export const counterSlice = createSlice({
name: 'counter',
// `createSlice` 将从 `initialState` 参数推断 state 类型
initialState,
reducers: {
increment: state => {
state.value += 1
},
decrement: state => {
state.value -= 1
},
// 使用 PayloadAction 类型声明 `action.payload` 的内容
incrementByAmount: (state, action: PayloadAction<number>) => {
state.value += action.payload
}
}
})

export const { increment, decrement, incrementByAmount } = counterSlice.actions
// 选择器等其他代码可以使用导入的 `RootState` 类型
export const selectCount = (state: RootState) => state.counter.value

export default counterSlice.reducer

生成的 action creator 将根据你为 reducer 提供的 PayloadAction<T> 类型来校验 payload 参数类型的正确性。例如,incrementByAmount 需要一个“数字”作为其参数。

在某些情况下,TypeScript 可能会对初始 state 进行不必要的类型收束. 如果发生这种情况,你可以通过使用 as 转换初始 state 来解决它,而不是声明变量的类型:

// 解决方法:强制转换 state 而不是声明变量类型
const initialState = {
value: 0
} as CounterState

在组件中使用标注过类型的钩子

在组件文件中,从 React-Redux 导入预类型的钩子而不是标准的钩子。

features/counter/Counter.tsx
import React from 'react'

import { useAppSelector, useAppDispatch } from 'app/hooks'

import { decrement, increment } from './counterSlice'

export function Counter() {
// `state` 参数已正确推断为 `RootState` 类型
const count = useAppSelector(state => state.counter.value)
const dispatch = useAppDispatch()

// 省略渲染逻辑
}

完整的 Counter 应用示例

这是运行在 CodeSandbox 的完整 TS 计数器应用程序:

下一步是什么?

有关如何将 Redux Toolkit API 与 TypeScript 一起使用的详细信息,请参阅 “使用 TypeScript”页面