Skip to main content

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 本身推断这些类型,意味着它们会随着您添加更多状态切片或修改中间件设置而正确更新。

因为有了这些是类型定义,可以安全地直接从你的 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'

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

应用程序中使用

定义切片状态和动作类型

每个切片文件都应该为其初始状态值定义一个类型,以便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'

// Define a type for the slice state
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

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.

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

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

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

What's Next?

See the "Usage with TypeScript" page for extended details on how to use Redux Toolkit's APIs with TypeScript.