跳到主要內容

React Redux TypeScript 快速入門

您將學到的事
  • 如何設定和使用附有 TypeScript 的 Redux Toolkit 和 React Redux
先備條件

引言

歡迎來到 React Redux TypeScript 快速入門教學!本教學將簡要示範如何使用 TypeScript 搭配 Redux Toolkit 和 React-Redux

此頁面重點在於如何設定 TypeScript 方面的功能。有關 Redux 的定義、它的運作方式以及如何使用 Redux 的完整範例,請參閱Redux 核心文件中的教學課程

React Redux 從版本 8 開始也使用 TypeScript 編寫,並包含其自己的型別定義。

適用於 Create-React-App 的Redux+TS 範本隨附了這些已經設定好的範例。

資訊

最近更新的 @types/react@18 主要版本已變更元件定義,並移除預設情況下擁有 children 作為 prop。如果您在您的專案中有 @types/react 的多個副本,這會導致錯誤。要修復此問題,請告知您的套件管理員將 @types/react 解析為單一版本。詳情

https://github.com/facebook/react/issues/24304#issuecomment-1094565891

專案設定

定義根部狀態和傳送類型

Redux Toolkit 的 configureStore API 不需要任何其他類型。但是,您會想要擷取 RootState 類型和 Dispatch 類型,以便在需要時參考。從儲存體中推斷這些類型表示,當您加入更多狀態區段或修改 middleware 設定時,它們會正確更新。

由於這些是類型,因此,可以從您的儲存體設定檔案(例如 app/store.ts)直接匯出,並將它們直接匯入其他檔案。

app/store.ts
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

定義類型化勾子

雖然可以將 RootStateAppDispatch 類型匯入每個元件,但針對您的應用程式使用 useDispatchuseSelector 勾子建立類型化版本會更好。基於以下兩個原因,這很重要:

  • 對於 useSelector,這樣可以免除您每次都需要輸入 (state: RootState) 的困擾
  • 對於 useDispatch,預設的 Dispatch 類型不瞭解 thunks。為了正確傳送 thunks,您需要使用儲存體中特定的自訂 AppDispatch 類型(其中包含 thunk middleware 類型),並將其與 useDispatch 搭配使用。新增預先類型化的 useDispatch 勾子,這樣就不會忘記在需要的地方匯入 AppDispatch

由於這些是實際的變數,而非類型,因此將它們定義在一個獨立檔案中(例如 app/hooks.ts)很重要,而非 Store 設定檔案中。這樣便可將它們匯入需要使用這些 Hook 的任何元件檔案,並且避免潛在的環狀匯入相依問題。

app/hooks.ts
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>()

應用程式使用

定義區塊狀態和動作類型

每個區塊檔案都應該針對其初始狀態值定義一個類型,如此一來 createSlice 才能在每一個 case reducer 中正確地推斷出 state 的類型。

所有生成的動作都應該使用 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

所生成的動作建立器將被正確地設定為接受一個 payload 參數,其基礎是你為 reducer 提供的 PayloadAction<T> 類型。例如,incrementByAmount 需要一個 number 作為其參數。

在某些情況下,TypeScript 可能會不必要地收緊初始狀態的類型。如果發生這種情況,你可以透過使用 as 轉換初始狀態來解決這個問題,而不是宣告變數的類型

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

在元件中使用型別化 Hook

在元件檔案中,匯入預先型別化的 Hook,而不是來自 React-Redux 的標準 Hook。

features/counter/Counter.tsx
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
}

下一步是什麼?

請參閱「使用 TypeScript」頁面,以取得關於如何將 Redux Toolkit 的 API 與 TypeScript 結合使用的詳細說明。