React Redux TypeScript 快速入門
- 如何設定和使用附有 TypeScript 的 Redux Toolkit 和 React Redux
- 認識 React Hook
- 了解 Redux 術語和概念
- 了解 TypeScript 語法和概念
引言
歡迎來到 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
)直接匯出,並將它們直接匯入其他檔案。
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
定義類型化勾子
雖然可以將 RootState
和 AppDispatch
類型匯入每個元件,但針對您的應用程式使用 useDispatch
與 useSelector
勾子建立類型化版本會更好。基於以下兩個原因,這很重要:
- 對於
useSelector
,這樣可以免除您每次都需要輸入(state: RootState)
的困擾 - 對於
useDispatch
,預設的Dispatch
類型不瞭解 thunks。為了正確傳送 thunks,您需要使用儲存體中特定的自訂AppDispatch
類型(其中包含 thunk middleware 類型),並將其與useDispatch
搭配使用。新增預先類型化的useDispatch
勾子,這樣就不會忘記在需要的地方匯入AppDispatch
。
由於這些是實際的變數,而非類型,因此將它們定義在一個獨立檔案中(例如 app/hooks.ts
)很重要,而非 Store 設定檔案中。這樣便可將它們匯入需要使用這些 Hook 的任何元件檔案,並且避免潛在的環狀匯入相依問題。
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 編譯器可以正確處理類型。這在撰寫選擇器函式的使用案例中可能是必要的。
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。
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 結合使用的詳細說明。