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

React Redux TypeScript クイックスタート

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

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などの別のファイルに定義することが重要です。これにより、フックを使用する必要がある任意のコンポーネントファイルにインポートでき、潜在的な循環インポート依存関係の問題を回避できます。

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コンパイラは型の循環インポートを正しく処理できます。これは、セレクター関数を書くなどのユースケースで必要になる場合があります。

features/counter/counterSlice.ts
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の標準フックではなく、事前に型付けされたフックをインポートします。

features/counter/Counter.tsx
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での使用」ページを参照してください。