React Redux TypeScript 快速入门
¥React Redux TypeScript Quick Start
如何通过 TypeScript 设置和使用 Redux Toolkit 和 React Redux
¥How to set up and use Redux Toolkit and React Redux with TypeScript
React 钩子 知识
¥Knowledge of React Hooks
对 Redux 术语和概念 的理解
¥Understanding of Redux terms and concepts
了解 TypeScript 语法和概念
¥Understanding of TypeScript syntax and concepts
介绍
¥Introduction
欢迎来到 React Redux TypeScript 快速入门教程!本教程将简要展示如何将 TypeScript 与 Redux Toolkit 和 React-Redux 结合使用。
¥Welcome to the React Redux TypeScript Quick Start tutorial! This tutorial will briefly show how to use TypeScript with Redux Toolkit and React-Redux.
本页仅重点介绍如何设置 TypeScript 元素。有关 Redux 是什么、它如何工作以及如何使用 Redux 的完整示例的解释,请参阅 Redux 核心文档教程。
¥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, see the Redux core docs tutorials.
从版本 8 开始,React Redux 也是用 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.
最近更新的 @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
项目设置
¥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.
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 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. 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.
import { useDispatch, useSelector } from 'react-redux'
import type { RootState, AppDispatch } 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.
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
生成的操作创建者将被正确键入以接受基于你为 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.
import React, { useState } 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
}
下一步是什么?
¥What's Next?
有关如何将 Redux Toolkit 的 API 与 TypeScript 结合使用的详细信息,请参阅 "与 TypeScript 一起使用" 页。
¥See the "Usage with TypeScript" page for extended details on how to use Redux Toolkit's APIs with TypeScript.