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

Redux Toolkit: 今日のReduxの使い方

Redux Toolkitとは?

Redux Toolkit (略して"RTK") は、Reduxロジックを書くための公式推奨アプローチです。 @reduxjs/toolkit パッケージは、コアの redux パッケージをラップし、Reduxアプリの構築に不可欠と考えるAPIメソッドと一般的な依存関係を含んでいます。Redux Toolkitは、推奨されるベストプラクティスを組み込み、ほとんどのReduxタスクを簡素化し、よくある間違いを防ぎ、Reduxアプリケーションの記述を容易にします。

今日、どんな Reduxロジックを書いていても、必ず Redux Toolkitを使ってそのコードを書くべきです!

RTKには、ストアの設定reducerの作成と不変の更新ロジックの記述、さらには状態の「スライス」全体を一度に作成するなど、多くの一般的なユースケースを簡素化するユーティリティが含まれています。

最初のプロジェクトを設定するReduxの初心者でも、既存のアプリケーションを簡素化したい経験豊富なユーザーでも、Redux Toolkit は、Reduxコードの改善に役立ちます。

ヒント

Redux Toolkitを使った「モダンRedux」の使い方については、以下のページをご覧ください。

Redux ToolkitとReduxコアの違い

「Redux」とは?

最初に、「Reduxとは何か?」と問う必要があります。

Reduxとは、実際には

  • 「グローバル」状態を含む単一のストア
  • アプリで何かが起こったときに、プレーンオブジェクトアクションをストアにディスパッチする
  • それらのアクションを見て、不変に更新された状態を返す純粋なreducer関数

必須ではありませんが、Reduxコードには通常、以下も含まれます

  • それらのアクションオブジェクトを生成するアクションクリエーター
  • 副作用を可能にするミドルウェア
  • 副作用を持つ同期または非同期ロジックを含むThunk関数
  • IDによるアイテムの検索を可能にする正規化された状態
  • 派生データを最適化するための、Reselectライブラリを使用したメモ化されたセレクター関数
  • アクション履歴と状態の変化を表示するためのRedux DevTools拡張機能
  • アクション、状態、その他の関数のTypeScript型

さらに、Reduxは通常、ReactコンポーネントがReduxストアと通信できるようにするために、React-Reduxライブラリと共に使用されます。

Reduxコアは何をするのか?

Reduxコアは、非常に小さく、意図的に独断的でないライブラリです。いくつかの小さなAPIプリミティブを提供します。

  • Reduxストアを実際に作成するための createStore
  • 複数のスライスreducerを1つの大きなreducerに結合するための combineReducers
  • 複数のミドルウェアをストアエンハンサーに結合するための applyMiddleware
  • 複数のストアエンハンサーを1つのストアエンハンサーに結合するための compose

それ以外は、アプリ内の他のすべてのRedux関連ロジックは、すべて自分で書く必要があります。

良いニュースは、Reduxは多くの異なる方法で使用できるということです。悪いニュースは、コードの記述を容易にするヘルパーがないことです。

たとえば、reducer関数は単なる関数です。Redux Toolkit以前は、通常、switch文と手動更新を使用してreducerを記述していました。また、おそらく手書きのアクションクリエーターとアクションタイプの定数も一緒に持っていたでしょう。

レガシーの手書きReduxの使用法
const ADD_TODO = 'ADD_TODO'
const TODO_TOGGLED = 'TODO_TOGGLED'

export const addTodo = text => ({
type: ADD_TODO,
payload: { text, id: nanoid() }
})

export const todoToggled = id => ({
type: TODO_TOGGLED,
payload: { id }
})

export const todosReducer = (state = [], action) => {
switch (action.type) {
case ADD_TODO:
return state.concat({
id: action.payload.id,
text: action.payload.text,
completed: false
})
case TODO_TOGGLED:
return state.map(todo => {
if (todo.id !== action.payload.id) return todo

return {
...todo,
completed: !todo.completed
}
})
default:
return state
}
}

このコードは、特にreduxコアライブラリのAPIに依存していません。しかし、これは書くべきコードがたくさんあります。不変の更新には、手書きのオブジェクトスプレッドと配列操作がたくさん必要で、その過程で誤って状態を変化させてしまうミスを犯しやすい(常にReduxバグの第一の原因!)でした。また、厳密には必須ではありませんが、1つの機能のコードをactions/todos.jsconstants/todos.jsreducers/todos.jsのような複数のファイルに分散させるのが一般的でした。

さらに、ストアのセットアップには、通常、Thunkのような一般的に使用されるミドルウェアを追加し、Redux DevTools拡張機能のサポートを有効にするための一連の手順が必要でした。これらはほとんどすべてのReduxアプリで使用される標準ツールですが。

Redux Toolkitは何をするのか?

これらはもともと Reduxドキュメントに示されていたパターンでしたが、残念ながら非常に冗長で反復的なコードをたくさん必要とします。この定型的なコードのほとんどは、Reduxを使用するために必ずしも必要ではありません。その上、定型的なコードは、ミスをする機会を増やしました。

Redux Toolkitは、手書きのReduxロジックから「定型文」を排除し、よくある間違いを防ぎ、標準的なReduxタスクを簡素化するAPIを提供するために、特別に作成されました。.

Redux Toolkitは、すべてのReduxアプリで最もよく行うことを簡素化する2つの主要なAPIから始まります。

  • configureStore は、reducerの結合、Thunkミドルウェアの追加、Redux DevTools統合の設定など、適切に設定されたReduxストアを1回の関数呼び出しで設定します。また、名前付きオプションパラメータを使用するため、createStoreよりも設定が簡単です。
  • createSlice を使用すると、Immerライブラリを使用して、スプレッドを必要とせずに、state.value = 123のような「ミューテーション」JS構文を使用して不変の更新を記述するreducerを作成できます。また、各reducerのアクションクリエーター関数を自動的に生成し、reducerの名前に基づいてアクションタイプの文字列を内部的に生成します。最後に、TypeScriptでうまく動作します。

つまり、あなたが書くコードは劇的にシンプルになる可能性があります。たとえば、同じtodos reducerは、次のようにすることができます。

features/todos/todosSlice.js
import { createSlice } from '@reduxjs/toolkit'

const todosSlice = createSlice({
name: 'todos',
initialState: [],
reducers: {
todoAdded(state, action) {
state.push({
id: action.payload.id,
text: action.payload.text,
completed: false
})
},
todoToggled(state, action) {
const todo = state.find(todo => todo.id === action.payload)
todo.completed = !todo.completed
}
}
})

export const { todoAdded, todoToggled } = todosSlice.actions
export default todosSlice.reducer

すべてのアクションクリエーターとアクションタイプは自動的に生成され、reducerコードは短く、理解しやすくなります。また、それぞれの場合に実際に何が更新されているかが、はるかに明確になります。

configureStore を使用すると、ストアのセットアップは次のように簡素化できます。

app/store.js
import { configureStore } from '@reduxjs/toolkit'
import todosReducer from '../features/todos/todosSlice'
import filtersReducer from '../features/filters/filtersSlice'

export const store = configureStore({
reducer: {
todos: todosReducer,
filters: filtersReducer
}
})

この1つのconfigureStore呼び出しは、手動で行っていた通常のセットアップ作業をすべて自動的に行うことに注意してください。

  • スライスreducerは自動的にcombineReducers()に渡されました。
  • redux-thunkミドルウェアが自動的に追加されました。
  • 偶発的な突然変異をキャッチするための開発モードミドルウェアが追加されました。
  • Redux DevTools拡張機能が自動的に設定されました。
  • ミドルウェアとDevToolsエンハンサーがまとめて構成され、ストアに追加されました。

同時に、configureStore は、ユーザーがこれらのデフォルトの動作を変更できるオプションを提供します(Thunkをオフにしてsagaを追加したり、本番環境でDevToolsを無効にするなど)。

そこから、Redux Toolkitには、一般的なReduxタスクのための他のAPIが含まれています。

  • createAsyncThunk: 標準的な「非同期リクエストの前後にアクションをディスパッチする」パターンを抽象化します。
  • createEntityAdapter: 正規化された状態に対するCRUD操作のための、事前に構築されたreducerとセレクターです。
  • createSelector: メモ化されたセレクターのための、標準のReselect APIの再エクスポートです。
  • createListenerMiddleware: ディスパッチされたアクションに応じてロジックを実行するための副作用ミドルウェアです。

最後に、RTKパッケージには、Reduxアプリの完全なデータフェッチとキャッシュソリューションである「RTK Query」も、個別のオプションの@reduxjs/toolkit/queryエントリポイントとして含まれています。エンドポイント(REST、GraphQL、または任意の非同期関数)を定義し、データのフェッチ、読み込み状態の更新、結果のキャッシュを完全に管理するreducerとミドルウェアを生成します。また、const { data, isFetching } = useGetPokemonQuery('pikachu')のように、コンポーネントでデータを取得するために使用できるReactフックも自動的に生成します。

これらのAPIはそれぞれ完全にオプションであり、特定のユースケース向けに設計されており、アプリで実際に使用するAPIを選択できます。しかし、これらのAPIはすべて、これらのタスクに役立つため、強く推奨されています。

Redux Toolkit は、依然として "Redux" であることに注意してください! 更新にはアクションオブジェクトがディスパッチされ、リデューサーは状態を不変的に更新します。さらに、非同期ロジック用のサンクを記述したり、正規化された状態を管理したり、TypeScript でコードを型付けしたり、DevTools を使用したりすることも可能です。同じ結果を得るために *あなた* が書くコードがはるかに少なくなっているだけです!

Redux Toolkit を使用してほしい理由

Redux のメンテナーとしての私たちの意見は、

ヒント

すべて の Redux ユーザーに Redux Toolkit を使って Redux コードを書いてほしいということです。なぜなら、コードが *簡素化* され、Redux でよくある間違いやバグがなくなります!

初期の Redux パターンの「ボイラープレート」と複雑さは、Redux に *必要な* 部分ではありませんでした。これらのパターンが存在したのは、

  • オリジナルの「Flux アーキテクチャ」でも同様のアプローチが使用されていたため
  • 初期の Redux ドキュメントでは、コードをタイプ別に異なるファイルに分割できるように、アクションタイプの定数などが示されていたため
  • JavaScript はデフォルトで mutable な言語であり、immutable な更新を行うには、手動でオブジェクトのスプレッドや配列の更新が必要だったため
  • Redux は元々わずか数週間で構築され、意図的に少数の API プリミティブとして設計されたため

さらに、Redux コミュニティは、ボイラープレートを追加する特定のアプローチを採用してきました。

  • 副作用を記述するための一般的なアプローチとして、redux-saga ミドルウェアの使用を重視していること
  • Redux アクションオブジェクトの TS タイプを手書きし、ユニオンタイプを作成して、タイプレベルでディスパッチできるアクションを制限すること

長年にわたり、私たちは人々が実際に Redux をどのように使用しているかを見てきました。コミュニティが、アクションタイプやクリエーターの生成、非同期ロジックと副作用、データフェッチなどのタスクのために、数百のアドオンライブラリをどのように記述してきたかを見てきました。また、状態を誤って変更したり、単純な状態更新を行うためだけに数十行のコードを書いたり、コードベースがどのように連携しているかを追跡するのが難しいなど、ユーザーにとって常に悩みの種となってきた問題も見てきました。私たちは、Redux を学習して使用しようとして、すべてのパーツがどのように組み合わされるかを理解するのに苦労し、概念の数と追加で書かなければならないコードの量に混乱している何千人ものユーザーを支援してきました。私たちは、ユーザーがどのような問題に直面しているかを *知って* います。

私たちは、Redux Toolkit をこれらの問題を解決するために特別に設計しました!

  • Redux Toolkit は、ストアのセットアップを単一の明確な関数呼び出しに簡素化しますが、必要に応じてストアのオプションを完全に設定する機能は保持しています。
  • Redux Toolkit は、Redux のバグの第一の原因となってきた、偶発的なミューテーションを排除します。
  • Redux Toolkit では、アクションクリエーターやアクションタイプを手書きする必要がなくなります。
  • Redux Toolkit では、手動でエラーが発生しやすい immutable な更新ロジックを記述する必要がなくなります。
  • Redux Toolkit を使用すると、Redux 機能のコードを複数のファイルに分散させるのではなく、1 つのファイルに簡単に記述できます。
  • Redux Toolkit は優れた TS サポートを提供し、優れた型安全性を提供し、コードで定義する必要のある型の数を最小限に抑えるように設計された API を備えています。
  • RTK Query を使用すると、データのフェッチと読み込み状態の追跡を管理するためのサンク、リデューサー、アクションクリエーター、または effect フックを *一切* 書く必要がなくなります。

このため、

ヒント

私たちは、ユーザーに Redux Toolkit (@reduxjs/toolkit パッケージ) を使用することを *推奨* し、新しい Redux コードにはレガシーの redux コアパッケージを *使用しない* ことを推奨しています!

既存のアプリケーションの場合でも、少なくとも createStoreconfigureStore に切り替えることをお勧めします。開発モードのミドルウェアは、既存のコードベースで偶発的なミューテーションやシリアライズ可能性のエラーをキャッチするのにも役立ちます。また、最もよく使用しているリデューサー (および今後作成するリデューサー) を createSlice に切り替えることをお勧めします。コードが短く理解しやすくなり、安全性の向上により、今後時間と労力を節約できます。

redux コアパッケージはまだ動作しますが、現在では廃止されていると考えています。そのすべての API は @reduxjs/toolkit からも再エクスポートされており、configureStorecreateStore が行うすべてのことを行いますが、デフォルトの動作と設定可能性が向上しています。

Redux Toolkit があなたのために行っていることをよりよく理解するために、低レベルの概念を理解することは *役立ちます*。そのため、「Redux Fundamentals」チュートリアルでは、抽象化なしで Redux の仕組みを紹介しています。*しかし*、これらの例は学習ツールとしてのみ示されており、最後は Redux Toolkit が古い手書きの Redux コードをどのように簡素化するかを示しています。

redux コアパッケージを単独で使用している場合、コードは引き続き動作します。しかし、@reduxjs/toolkit に切り替え、Redux Toolkit API を使用するようにコードを更新することを強くお勧めします!

詳細情報

詳細については、これらのドキュメントページとブログ投稿を参照してください。