React Redux TypeScript クイックスタート
- Redux Toolkit と React Redux を TypeScript で設定して使用する方法
- React Hooks の知識
- Redux の用語と概念の理解
- TypeScript の構文と概念の理解
はじめに
React Redux TypeScript クイックスタートチュートリアルへようこそ!このチュートリアルでは、Redux ToolkitとReact-ReduxでTypeScriptを使用する方法を簡単に説明します。
このページでは、TypeScriptの設定方法のみに焦点を当てています。Reduxとは何か、どのように機能するのか、Reduxの使用方法の完全な例については、Reduxコアドキュメントのチュートリアルを参照してください。
React Reduxもバージョン8以降はTypeScriptで記述されており、独自の型定義も含まれています。
Create-React-App用のRedux+TSテンプレートには、これらのパターンがすでに設定された動作例が付属しています。
最近更新された@types/react@18
のメジャーバージョンでは、コンポーネント定義が変更され、デフォルトでchildren
がpropsとして含まれなくなりました。これにより、プロジェクトに@types/react
のコピーが複数存在する場合にエラーが発生します。これを修正するには、パッケージマネージャーに@types/react
を単一のバージョンに解決するように指示してください。詳細
https://github.com/facebook/react/issues/24304#issuecomment-1094565891
プロジェクトのセットアップ
ルートの状態とディスパッチの型を定義する
Redux ToolkitのconfigureStore
APIは、追加の型定義を必要としません。ただし、必要に応じて参照できるように、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
などの別のファイルに定義することが重要です。これにより、フックを使用する必要がある任意のコンポーネントファイルにインポートでき、潜在的な循環インポート依存関係の問題を回避できます。
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
は各ケースリデューサーでstate
の型を正しく推論できます。
生成されたすべてのアクションは、Redux ToolkitのPayloadAction<T>
型を使用して定義する必要があります。これは、action.payload
フィールドの型をジェネリック引数として受け取ります。
ここでは、ストアファイルから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
生成されたアクションクリエーターは、リデューサーに提供したPayloadAction<T>
型に基づいて、payload
引数を受け入れるように正しく型付けされます。たとえば、incrementByAmount
には引数としてnumber
が必要です。
場合によっては、TypeScriptが初期状態の型を不必要に厳しくすることがあります。その場合は、変数の型を宣言するのではなく、as
を使用して初期状態をキャストすることで回避できます。
// Workaround: cast state instead of declaring variable type
const initialState = {
value: 0,
} as CounterState
コンポーネントで型付きフックを使用する
コンポーネントファイルでは、React-Reduxの標準フックではなく、事前に型付けされたフックをインポートします。
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
}
次は何をしますか?
Redux ToolkitのAPIをTypeScriptで使用する方法の詳細については、「TypeScriptでの使用」ページを参照してください。