使用 TypeScript 的方式
從 React-Redux v8 開始,React-Redux 已完全用 TypeScript 編寫,而類型已包含在已發布的套件中。這些類型還會匯出一些輔助函式,以便於撰寫 Redux store 和 React 元件之間的類型安全介面。
最近更新的 @types/react@18
主要版本已變更元件定義,移除預設的 children
為屬性。如果你有專案中的 @types/react
有多個拷貝,這會造成錯誤。要修正這個問題,請告訴套件管理員將 @types/react
解析為單一版本。詳細資訊
https://github.com/facebook/react/issues/24304#issuecomment-1094565891
標準 Redux Toolkit 專案設定與 TypeScript
我們假設典型的 Redux 專案同時使用 Redux Toolkit 與 React Redux。
Redux Toolkit (RTK) 是編寫現代 Redux 邏輯的標準做法。RTK 已用 TypeScript 編寫,其 API 的設計在 TypeScript 使用的體驗上相當好。
適用於 Create-React-App 的 Redux+TS 範本附帶了已設定好這些模式的實作範例。
定義根狀態和執行類型
使用 configureStore 不需要任何額外的型別。但您至少會希望萃取出 RootState
型別和 Dispatch
型別,以便可以在需要時參考它們。從儲存本身推斷出這些型別意謂著,當您加入更多狀態區段或修改中間軟體設定時,這些型別會自動正確地更新。
由於那些是型別,因此可以從您的儲存設定檔安全地匯出它們,例如 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
定義有型別的 Hook
雖然可以將 RootState
和 AppDispatch
型別匯入到每個元件中,但最好的方式是為您的應用程式使用 useDispatch
和 useSelector
Hook 建立有型別的版本。這有幾個重要原因
- 對於
useSelector
,它讓您無需每次都輸入(state: RootState)
- 至於
useDispatch
,預設的Dispatch
型別不包含 Thunk 或其他中間軟體的資訊。若要正確地執行 Thunk,您需要使用儲存中的自訂AppDispatch
型別,其中包含 Thunk 中間軟體型別,並將其與useDispatch
搭配使用。加入一個有型別的useDispatch
Hook 可以避免您忘記在需要的地方匯入AppDispatch
。
由於這些是實際變數,而不是型別,因此應該在個別的檔案中定義它們,例如 app/hooks.ts
,而不是儲存設定檔。這讓您可以將它們匯入到任何需要使用 Hook 的元件檔案,並避免可能發生的循環匯入相依性問題。
.withTypes()
在過去,「為 Hook 加入型別」的方法因應應用程式的設定而有些許不同。結果會類似下列片段
import type { TypedUseSelectorHook } from 'react-redux'
import { useDispatch, useSelector, useStore } from 'react-redux'
import type { AppDispatch, AppStore, RootState } from './store'
// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch: () => AppDispatch = useDispatch
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
export const useAppStore: () => AppStore = useStore
React Redux v9.1.0 已在每個 Hook 中加入新的 .withTypes
方法,就像 Redux Toolkit 中 createAsyncThunk
的 .withTypes
方法。
設定現在會變成:
import { useDispatch, useSelector, useStore } from 'react-redux'
import type { AppDispatch, AppStore, 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>()
export const useAppStore = useStore.withTypes<AppStore>()
手動輸入 Hooks
我們建議您使用上面所示的預先輸入的 useAppSelector
和 useAppDispatch
Hooks。如果您不喜歡使用這些,這裡是手動輸入 Hooks 的方法。
輸入 useSelector
Hook
為 useSelector
所用的選取函數撰寫時,您應該明確定義 state
參數的類型。TS 應該可以推斷選取器的回傳類型,它將會重複用作 useSelector
Hook 的回傳類型
interface RootState {
isOn: boolean
}
// TS infers type: (state: RootState) => boolean
const selectIsOn = (state: RootState) => state.isOn
// TS infers `isOn` is boolean
const isOn = useSelector(selectIsOn)
這也可以內嵌完成
const isOn = useSelector((state: RootState) => state.isOn)
輸入 useDispatch
Hook
預設情況下,useDispatch
的回傳值是 Redux 核心類型所定義的標準 Dispatch
類型,所以不需要宣告
const dispatch = useDispatch()
如果您有自訂版本的 Dispatch
類型,您可以明確使用該類型
// store.ts
export type AppDispatch = typeof store.dispatch
// MyComponent.tsx
const dispatch: AppDispatch = useDispatch()
輸入 connect
高階元件
自動推斷連結的 Props
connect
包含兩個依序呼叫的函數。第一個函數接受 mapState
和 mapDispatch
作為引數,然後回傳第二個函數。第二個函數接受要包裝的元件,然後回傳新的包裝元件,向下傳遞 mapState
和 mapDispatch
的 Props。一般來說,兩個函數會一起呼叫,例如 connect(mapState, mapDispatch)(MyComponent)
。
此套件包含一個輔助類型 ConnectedProps
,可以從第一個函數中取出 mapStateToProps
和 mapDispatchToProps
的回傳類型。這表示如果您將 connect
呼叫拆成兩個步驟,所有「Redux 的 Props」都可以自動推斷出來,不需要手動輸入。如果您過去一直使用 React-Redux,這種做法可能會讓您覺得不習慣,但它確實大幅簡化了類型宣告。
import { connect, ConnectedProps } from 'react-redux'
interface RootState {
isOn: boolean
}
const mapState = (state: RootState) => ({
isOn: state.isOn,
})
const mapDispatch = {
toggleOn: () => ({ type: 'TOGGLE_IS_ON' }),
}
const connector = connect(mapState, mapDispatch)
// The inferred type will look like:
// {isOn: boolean, toggleOn: () => void}
type PropsFromRedux = ConnectedProps<typeof connector>
然後 ConnectedProps
的回傳類型可以用來輸入您的 Props 物件。
interface Props extends PropsFromRedux {
backgroundColor: string
}
const MyComponent = (props: Props) => (
<div style={{ backgroundColor: props.backgroundColor }}>
<button onClick={props.toggleOn}>
Toggle is {props.isOn ? 'ON' : 'OFF'}
</button>
</div>
)
export default connector(MyComponent)
因為類型可以隨意定義順序,所以如果您願意,您還是可以在宣告連接器之前宣告您的元件。
// alternately, declare `type Props = PropsFromRedux & {backgroundColor: string}`
interface Props extends PropsFromRedux {
backgroundColor: string;
}
const MyComponent = (props: Props) => /* same as above */
const connector = connect(/* same as above*/)
type PropsFromRedux = ConnectedProps<typeof connector>
export default connector(MyComponent)
手動輸入 connect
connect
的高階元件有點難以設定型別,因為有 3 個 prop 的來源:mapStateToProps
、mapDispatchToProps
,以及從父元件傳遞過來的 props。以下是手動執行此項操作的完整範例。
import { connect } from 'react-redux'
interface StateProps {
isOn: boolean
}
interface DispatchProps {
toggleOn: () => void
}
interface OwnProps {
backgroundColor: string
}
type Props = StateProps & DispatchProps & OwnProps
const mapState = (state: RootState) => ({
isOn: state.isOn,
})
const mapDispatch = {
toggleOn: () => ({ type: 'TOGGLE_IS_ON' }),
}
const MyComponent = (props: Props) => (
<div style={{ backgroundColor: props.backgroundColor }}>
<button onClick={props.toggleOn}>
Toggle is {props.isOn ? 'ON' : 'OFF'}
</button>
</div>
)
// Typical usage: `connect` is called after the component is defined
export default connect<StateProps, DispatchProps, OwnProps>(
mapState,
mapDispatch,
)(MyComponent)
也可以透過推論 mapState
和 mapDispatch
的型別,來稍微縮短這項作業。
const mapState = (state: RootState) => ({
isOn: state.isOn,
})
const mapDispatch = {
toggleOn: () => ({ type: 'TOGGLE_IS_ON' }),
}
type StateProps = ReturnType<typeof mapState>
type DispatchProps = typeof mapDispatch
type Props = StateProps & DispatchProps & OwnProps
不過,如果使用物件來定義 mapDispatch
的型別,並且參照 thunk,那麼推論的型別就會是錯誤的。
建議事項
Hooks API 與靜態型別一起使用通常會比較簡單。如果您想找一個最簡單的方式,來在 React-Redux 中使用靜態型別,請使用 Hooks API。
如果您要使用 connect
,我們建議使用 ConnectedProps<T>
方法來推論 Redux 的 props,因為這樣所需的明確型別宣告最少。
資源資訊
其他資訊,請參閱以下的資源資訊。
- Redux 文件:與 TypeScript 搭配使用:使用 Redux Toolkit、Redux 核心元件,還有 React Redux 和 TypeScript 的範例
- Redux Toolkit 文件:TypeScript 快速入門:說明如何使用 RTK 和 React-Redux Hooks API 和 TypeScript
- React+TypeScript 說明文件:一個使用 React 和 TypeScript 的綜合指南
- React + Redux in TypeScript 指南:使用 React 和 Redux 以及 TypeScript 的範例資訊