Skip to main content

Redux 工具包 TypeScript 快速入门

¥Redux Toolkit TypeScript Quick Start

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

    ¥How to set up and use Redux Toolkit and React-Redux with TypeScript

先决条件

介绍

¥Introduction

欢迎来到 Redux Toolkit TypeScript 快速入门教程!本教程将简要展示如何将 TypeScript 与 Redux Toolkit 和 React-Redux 结合使用。

¥Welcome to the Redux Toolkit TypeScript Quick Start tutorial! This tutorial will briefly show how to use TypeScript with Redux Toolkit and React-Redux.

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

¥This page focuses on just how to set up the TypeScript aspects . For explanations of what Redux is, how it works, and full examples of how to use Redux Toolkit, see the tutorials linked in the "Tutorials Index" page.

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

¥Redux Toolkit is already written in TypeScript, so its TS type definitions are built in.

从版本 8 开始,反应还原 也是用 TypeScript 编写的,并且还包含自己的类型定义。

¥React Redux is also written in TypeScript as of version 8, and also includes its own type definitions.

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

¥The Redux+TS template for Create-React-App comes with a working example of these patterns already configured.

项目设置

¥Project Setup

定义根状态和调度类型

¥Define Root State and Dispatch Types

Redux 工具包的 configureStore API 不需要任何额外的输入。但是,你将需要提取 RootState 类型和 Dispatch 类型,以便可以根据需要引用它们。从存储本身推断这些类型意味着当你添加更多状态片或修改中间件设置时它们会正确更新。

¥Redux Toolkit's configureStore API 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.

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

export 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

虽然可以将 RootStateAppDispatch 类型导入到每个组件中,但最好创建 useDispatchuseSelector 钩子的类型化版本以供在应用中使用。这很重要,原因如下:

¥While it's possible to import the RootState and AppDispatch types into each component, it's better to create 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 default Dispatch type does not know about thunks. In order to correctly dispatch thunks, you need to use the specific customized AppDispatch type from the store that includes the thunk middleware types, and use that with useDispatch. Adding a pre-typed useDispatch hook keeps you from forgetting to import AppDispatch 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.

app/hooks.ts
import { useDispatch, useSelector } from 'react-redux'
import type { AppDispatch, 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>()

应用使用

¥Application Usage

定义切片状态和操作类型

¥Define Slice State and Action Types

每个切片文件应该为其初始状态值定义一个类型,以便 createSlice 可以正确推断每种情况 reducer 中 state 的类型。

¥Each slice file should define a type for its initial state value, so that createSlice can correctly infer the type of state in each case reducer.

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

¥All generated actions should be defined using the PayloadAction<T> type from Redux Toolkit, which takes the type of the action.payload field as its generic argument.

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

¥You can safely import the RootState type from the store file here. It's a circular import, but the TypeScript compiler can correctly handle that for types. This may be needed for use cases like writing selector functions.

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

// Define a type for the slice state
export interface CounterState {
value: number
}

// Define the initial state using that type
const initialState: CounterState = {
value: 0
}

export const counterSlice = createSlice({
name: 'counter',
// `createSlice` will infer the state type from the `initialState` argument
initialState,
reducers: {
increment: state => {
state.value += 1
},
decrement: state => {
state.value -= 1
},
// Use the PayloadAction type to declare the contents of `action.payload`
incrementByAmount: (state, action: PayloadAction<number>) => {
state.value += action.payload
}
}
})

export const { increment, decrement, incrementByAmount } = counterSlice.actions

// Other code such as selectors can use the imported `RootState` type
export const selectCount = (state: RootState) => state.counter.value

export default counterSlice.reducer

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

¥The generated action creators will be correctly typed to accept a payload argument based on the PayloadAction<T> type you provided for the reducer. For example, incrementByAmount requires a number as its argument.

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

¥In some cases, TypeScript may unnecessarily tighten the type of the initial state. If that happens, you can work around it by casting the initial state using as, instead of declaring the type of the variable:

// Workaround: cast state instead of declaring variable type
const initialState = {
value: 0
} as CounterState

在组件中使用类型化钩子

¥Use Typed Hooks in Components

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

¥In component files, import the pre-typed hooks instead of the standard hooks from React-Redux.

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

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

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

export function Counter() {
// The `state` arg is correctly typed as `RootState` already
const count = useAppSelector(state => state.counter.value)
const dispatch = useAppDispatch()

// omit rendering logic
}

完整的计数器应用示例

¥Full Counter App Example

以下是运行 CodeSandbox 时的完整 TS 计数器应用:

¥Here's the complete TS counter application as a running CodeSandbox:

下一步是什么?

¥What's Next?

我们建议阅读 完整的 "Redux 要点" 教程,它涵盖了 Redux Toolkit 中包含的所有关键部分、它们解决的问题以及如何使用它们构建实际应用。

¥We recommend going through the full "Redux Essentials" tutorial, which covers all of the key pieces included in Redux Toolkit, what problems they solve, and how to use them to build real-world applications.

你可能还想通读 "Redux 基础知识" 教程,它将让你全面了解 Redux 是如何工作的、Redux Toolkit 的作用以及如何正确使用它。

¥You may also want to read through the "Redux Fundamentals" tutorial, which will give you a complete understanding of how Redux works, what Redux Toolkit does, and how to use it correctly.

最后,请参阅 "与 TypeScript 一起使用" 页 了解有关如何将 Redux Toolkit 的 API 与 TypeScript 结合使用的详细信息。

¥Finally, see the "Usage with TypeScript" page for extended details on how to use Redux Toolkit's APIs with TypeScript.