跳到主要內容

使用 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,並直接匯入到其他檔案中。

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

雖然可以將 RootStateAppDispatch 型別匯入到每個元件中,但最好的方式是為您的應用程式使用 useDispatchuseSelector Hook 建立有型別的版本。這有幾個重要原因

  • 對於 useSelector,它讓您無需每次都輸入 (state: RootState)
  • 至於 useDispatch,預設的 Dispatch 型別不包含 Thunk 或其他中間軟體的資訊。若要正確地執行 Thunk,您需要使用儲存中的自訂 AppDispatch 型別,其中包含 Thunk 中間軟體型別,並將其與 useDispatch 搭配使用。加入一個有型別的 useDispatch Hook 可以避免您忘記在需要的地方匯入 AppDispatch

由於這些是實際變數,而不是型別,因此應該在個別的檔案中定義它們,例如 app/hooks.ts,而不是儲存設定檔。這讓您可以將它們匯入到任何需要使用 Hook 的元件檔案,並避免可能發生的循環匯入相依性問題。

.withTypes()

在過去,「為 Hook 加入型別」的方法因應應用程式的設定而有些許不同。結果會類似下列片段

app/hooks.ts
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 方法。

設定現在會變成:

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

我們建議您使用上面所示的預先輸入的 useAppSelectoruseAppDispatch 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 包含兩個依序呼叫的函數。第一個函數接受 mapStatemapDispatch 作為引數,然後回傳第二個函數。第二個函數接受要包裝的元件,然後回傳新的包裝元件,向下傳遞 mapStatemapDispatch 的 Props。一般來說,兩個函數會一起呼叫,例如 connect(mapState, mapDispatch)(MyComponent)

此套件包含一個輔助類型 ConnectedProps,可以從第一個函數中取出 mapStateToPropsmapDispatchToProps 的回傳類型。這表示如果您將 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 的來源:mapStateToPropsmapDispatchToProps,以及從父元件傳遞過來的 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)

也可以透過推論 mapStatemapDispatch 的型別,來稍微縮短這項作業。

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,因為這樣所需的明確型別宣告最少。

資源資訊

其他資訊,請參閱以下的資源資訊。