跳到主要内容

 

Redux Toolkit TypeScript 快速入门

你将学到什么
  • 如何使用 TypeScript 设置和使用 Redux Toolkit 和 React-Redux
先决条件

介绍

欢迎来到 Redux Toolkit TypeScript 快速入门教程!本教程将简要介绍如何在 Redux Toolkit 中使用 TypeScript

本页主要关注如何设置 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)。

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

项目设置

定义根状态和调度类型

使用 configureStore 不应需要任何额外的类型定义。然而,你会想要提取 RootState 类型和 Dispatch 类型,以便在需要的时候可以引用它们。从 store 本身推断这些类型意味着,当你添加更多的状态切片或修改中间件设置时,它们会正确地更新。

由于这些是类型,所以可以直接从你的 store 设置文件(如 app/store.ts)中导出它们,并直接导入到其他文件中。

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

export 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 hooks 的类型化版本是更好的选择。这有几个重要的原因:

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

由于这些是实际的变量,而不是类型,所以重要的是要在一个单独的文件(如 app/hooks.ts)中定义它们,而不是在 store 设置文件中。这允许你将它们导入到任何需要使用 hooks 的组件文件中,并避免可能的循环导入依赖问题。

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

// 在你的应用中使用,而不是普通的 `useDispatch` 和 `useSelector`
export const useAppDispatch = useDispatch.withTypes<AppDispatch>()
export const useAppSelector = useSelector.withTypes<RootState>()

应用程序使用

定义切片状态和操作类型

每个切片文件都应定义其初始状态值的类型,这样 createSlice 可以正确地推断每个 case reducer 中 state 的类型。

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

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

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

// 为切片状态定义类型
interface CounterState {
value: number
}

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

export const counterSlice = createSlice({
name: 'counter',
// `createSlice` 将从 `initialState` 参数推断状态类型
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

生成的操作创建器将被正确地类型化,以接受基于你为 reducer 提供的 PayloadAction<T> 类型的 payload 参数。例如,incrementByAmount 需要一个 number 作为其参数。

在某些情况下,TypeScript 可能不必要地收紧初始状态的类型。如果发生这种情况,你可以通过使用 as 强制转换初始状态来解决这个问题,而不是声明变量的类型:

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

在组件中使用类型化的 Hooks

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

features/counter/Counter.tsx
import React, { useState } 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()

// 省略渲染逻辑
}

下一步是什么?

查看 “与 TypeScript 一起使用”页面,了解如何使用 Redux Toolkit 的 API 与 TypeScript 的详细信息。