メインコンテンツにスキップ

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 などのストア設定ファイルから直接エクスポートし、他のファイルに直接インポートしても安全です。

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()

以前は、アプリの設定でフックを「事前に型付け」するためのアプローチは多少異なっていました。結果は、以下のスニペットのようになるでしょう。

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 では、Redux Toolkit の createAsyncThunk にある .withTypes メソッドと同様に、これらの各フックに新しい .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>()

フックの手動型付け

上記で示した事前に型付けされた 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 つの関数で構成されています。最初の関数は、引数として mapStatemapDispatch を受け取り、2 番目の関数を返します。2 番目の関数は、ラップするコンポーネントを受け取り、mapStatemapDispatch から props を渡す新しいラッパーコンポーネントを返します。通常、両方の関数は connect(mapState, mapDispatch)(MyComponent) のように一緒に呼び出されます。

パッケージには、最初の関数から mapStateToPropsmapDispatchToProps の戻り値の型を抽出できるヘルパー型 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 つ (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 も参照している場合は壊れます。

推奨事項

フックAPIは、一般に静的型で使用する方が簡単です。React-Reduxで静的型を使用するための最も簡単なソリューションを探している場合は、フックAPIを使用してください。

connectを使用している場合は、Reduxからpropsを推論するために、ConnectedProps<T>アプローチを使用することをお勧めします。これは、明示的な型宣言が最も少ないためです。

リソース

詳細については、次の追加リソースを参照してください。