TypeScriptでの使用
React-Redux v8 以降、React-Redux は完全に TypeScript で記述されており、型は公開されたパッケージに含まれています。また、型は Redux ストアと React コンポーネント間の型安全なインターフェースをより簡単に記述できるようにするいくつかのヘルパーをエクスポートしています。
最近更新された @types/react@18
のメジャーバージョンでは、コンポーネント定義が変更され、デフォルトで children
が prop として削除されました。これにより、プロジェクトに @types/react
のコピーが複数存在する場合にエラーが発生します。これを修正するには、パッケージマネージャーに @types/react
を単一のバージョンに解決するように指示します。詳細
https://github.com/facebook/react/issues/24304#issuecomment-1094565891
TypeScriptを使った標準的なRedux Toolkitプロジェクトのセットアップ
典型的な 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
型付きフックの定義
RootState
型と AppDispatch
型を各コンポーネントにインポートすることも可能ですが、アプリケーションで使用するために useDispatch
および useSelector
フックの事前に型付けされたバージョンを作成する方が優れています。これは、いくつかの理由で重要です。
useSelector
の場合、(state: RootState)
を毎回型付けする必要がなくなります。useDispatch
の場合、デフォルトのDispatch
型は thunk やその他のミドルウェアについて認識していません。thunk を正しくディスパッチするには、thunk ミドルウェア型を含むストアからの特定のカスタマイズされたAppDispatch
型を使用し、それをuseDispatch
で使用する必要があります。事前に型付けされたuseDispatch
フックを追加すると、必要な場所にAppDispatch
をインポートするのを忘れるのを防ぐことができます。
これらは型ではなく実際の変数であるため、ストア設定ファイルではなく、app/hooks.ts
などの別のファイルで定義することが重要です。これにより、フックを使用する必要がある任意のコンポーネントファイルにそれらをインポートでき、潜在的な循環インポート依存関係の問題を回避できます。
.withTypes()
以前は、アプリの設定でフックを「事前に型付け」するためのアプローチは多少異なっていました。結果は、以下のスニペットのようになるでしょう。
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 では、Redux Toolkit の createAsyncThunk
にある .withTypes
メソッドと同様に、これらの各フックに新しい .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>()
フックの手動型付け
上記で示した事前に型付けされた useAppSelector
および useAppDispatch
フックを使用することをお勧めします。それらを使用しない場合は、フック自体を型付けする方法を次に示します。
useSelector
フックの型付け
useSelector
で使用するセレクター関数を作成するときは、state
パラメーターの型を明示的に定義する必要があります。そうすると、TS はセレクターの戻り値の型を推論できるようになり、それが useSelector
フックの戻り値の型として再利用されます。
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
フックの型付け
デフォルトでは、useDispatch
の戻り値は Redux コア型によって定義された標準の Dispatch
型であるため、宣言は必要ありません。
const dispatch = useDispatch()
Dispatch
型のカスタマイズされたバージョンがある場合は、その型を明示的に使用できます。
// store.ts
export type AppDispatch = typeof store.dispatch
// MyComponent.tsx
const dispatch: AppDispatch = useDispatch()
connect
高階コンポーネントの型付け
接続されたpropsの自動推論
connect
は、順番に呼び出される 2 つの関数で構成されています。最初の関数は、引数として mapState
と mapDispatch
を受け取り、2 番目の関数を返します。2 番目の関数は、ラップするコンポーネントを受け取り、mapState
と mapDispatch
から props を渡す新しいラッパーコンポーネントを返します。通常、両方の関数は connect(mapState, mapDispatch)(MyComponent)
のように一緒に呼び出されます。
パッケージには、最初の関数から mapStateToProps
と mapDispatchToProps
の戻り値の型を抽出できるヘルパー型 ConnectedProps
が含まれています。つまり、connect
呼び出しを 2 つのステップに分割した場合、「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
高階コンポーネントは、props のソースが 3 つ (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 も参照している場合は壊れます。
推奨事項
フックAPIは、一般に静的型で使用する方が簡単です。React-Reduxで静的型を使用するための最も簡単なソリューションを探している場合は、フックAPIを使用してください。
connect
を使用している場合は、Reduxからpropsを推論するために、ConnectedProps<T>
アプローチを使用することをお勧めします。これは、明示的な型宣言が最も少ないためです。
リソース
詳細については、次の追加リソースを参照してください。
- Reduxドキュメント:TypeScriptでの使用:Redux Toolkit、Reduxコア、およびReact ReduxをTypeScriptで使用する方法の例
- Redux Toolkitドキュメント:TypeScriptクイックスタート:RTKとReact-ReduxフックAPIをTypeScriptで使用する方法を示します。
- React+TypeScriptチートシート:ReactとTypeScriptを使用するための包括的なガイド
- React + Redux in TypeScriptガイド:ReactとReduxをTypeScriptで使用するためのパターンに関する豊富な情報