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

Redux Toolkit TypeScriptクイックスタート

学習内容
  • TypeScriptでRedux ToolkitとReact-Reduxをセットアップして使用する方法
前提条件

はじめに

Redux Toolkit TypeScriptクイックスタートチュートリアルへようこそ! **このチュートリアルでは、TypeScriptをRedux Toolkitと組み合わせて使用する方法を簡単に紹介します**。

このページでは、TypeScriptの側面の設定方法のみに焦点を当てています。Reduxとは何か、どのように動作するのか、Redux Toolkitの使用方法の完全な例については、「チュートリアルの概要」ページにリンクされているチュートリアルを参照してください

Redux Toolkitは既にTypeScriptで記述されているため、TS型定義が組み込まれています。

React Reduxは、NPM上の別の@types/react-redux 型定義パッケージに型定義を持っています。ライブラリ関数の型付けに加えて、型はReduxストアとReactコンポーネント間のタイプセーフなインターフェースを記述しやすくするためのヘルパーもエクスポートします。

React Redux v7.2.3以降、react-reduxパッケージは@types/react-reduxに依存しているため、型定義はライブラリと共に自動的にインストールされます。そうでない場合は、手動でインストールする必要があります(通常はnpm install @types/react-redux)。

Create-React-AppのRedux+TSテンプレートには、これらのパターンが既に設定された動作例が含まれています。

プロジェクトのセットアップ

ルート状態とディスパッチタイプの定義

configureStoreを使用する場合、追加の型定義は必要ありません。ただし、必要に応じて参照できるように、`RootState`型と`Dispatch`型を抽出する必要があります。これらの型をストア自体から推論することで、状態スライスを追加したり、ミドルウェア設定を変更したりすると、型が正しく更新されます。

これらは型なので、`app/store.ts`などのストア設定ファイルから直接エクスポートし、他のファイルに直接インポートしても安全です。

app/store.ts
import { configureStore } from '@reduxjs/toolkit'
// ...

export 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`型はサンクを認識しません。サンクを正しくディスパッチするには、サンクミドルウェア型を含むストアからの特定のカスタマイズされた`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 } from '@reduxjs/toolkit'
import type { 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,
} satisfies CounterState 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での使用」ページを参照してください。