Reduxの基礎、パート5:UIとReact
- ReduxストアがUIとどのように連携するか
- ReduxをReactで利用する方法
はじめに
パート4:ストアでは、Reduxストアの作成、アクションのディスパッチ、現在のステートの読み取りについて見てきました。また、ストアの内部構造、エンハンサーとミドルウェアを使ってストアに追加機能を持たせる方法、そしてRedux DevToolsを追加してアクションがディスパッチされる際にアプリ内部で何が起こっているかを確認する方法についても見てきました。
このセクションでは、Todoアプリのユーザーインターフェースを追加します。ReduxがUIレイヤー全体とどのように連携するかを見ていき、特にReduxがReactとどのように連携するかを説明します。
このページと「エッセンシャル」チュートリアルでは、最新のReact-Redux hooks APIの使用方法を教えています。旧式のconnect
APIも引き続き動作しますが、現在ではすべてのReduxユーザーにhooks APIの使用をお勧めします。
また、このチュートリアルの他のページでは、Reduxの背後にある原則と概念を説明するために、現在Reduxでアプリを構築するための正しいアプローチとして教えているRedux Toolkitを使用した「現代的なRedux」パターンよりも多くのコードを必要とする、意図的に古いスタイルのReduxロジックパターンを示しています。
Redux ToolkitとReact-Redux hooksを使って実世界のアプリで「Reduxを正しく使用する方法」の完全な例については、「Reduxエッセンシャル」チュートリアルをご覧ください。
ReduxとUIの統合
ReduxはスタンドアロンのJSライブラリです。すでに見たように、ユーザーインターフェースが設定されていなくてもReduxストアを作成して使用できます。これは、Reduxを任意のUIフレームワーク(またはUIフレームワークなしでも)で使用でき、クライアントとサーバーの両方で使用できることも意味します。React、Vue、Angular、Ember、jQuery、またはvanilla JavaScriptでReduxアプリを作成できます。
とはいえ、ReduxはReactとうまく連携するように特別に設計されています。ReactではUIを状態の関数として記述でき、Reduxには状態が含まれており、アクションに応じて状態が更新されます。
そのため、このチュートリアルでは、Todoアプリを作成する際にReactを使用し、ReduxをReactで使用する際の基本について説明します。
その部分に移る前に、ReduxがUIレイヤーと一般的にどのように相互作用するかを簡単に見てみましょう。
ReduxとUIの基本的な統合
ReduxをUIレイヤーで使用するには、いくつかの共通の手順が必要です。
- Reduxストアを作成する
- 更新をサブスクライブする
- サブスクリプションコールバック内
- 現在のストアステートを取得する
- このUI部分に必要なデータを抽出する
- データでUIを更新する
- 必要に応じて、初期状態を使用してUIをレンダリングする
- ReduxアクションをディスパッチしてUIの入力に応答する
パート1で見たカウンターアプリの例に戻って、これらの手順にどのように従っているかを確認しましょう。
// 1) Create a new Redux store with the `createStore` function
const store = Redux.createStore(counterReducer)
// 2) Subscribe to redraw whenever the data changes in the future
store.subscribe(render)
// Our "user interface" is some text in a single HTML element
const valueEl = document.getElementById('value')
// 3) When the subscription callback runs:
function render() {
// 3.1) Get the current store state
const state = store.getState()
// 3.2) Extract the data you want
const newValue = state.value.toString()
// 3.3) Update the UI with the new value
valueEl.innerHTML = newValue
}
// 4) Display the UI with the initial store state
render()
// 5) Dispatch actions based on UI inputs
document.getElementById('increment').addEventListener('click', function () {
store.dispatch({ type: 'counter/incremented' })
})
どのUIレイヤーを使用している場合でも、ReduxはすべてのUIで同じように動作します。実際の実装は、通常、パフォーマンスを最適化するために少し複雑になっていますが、毎回同じ手順です。
Reduxは別のライブラリであるため、特定のUIフレームワークでReduxを使用するのに役立つさまざまな「バインディング」ライブラリがあります。これらのUIバインディングライブラリは、ストアへのサブスクライブや、ステートの変更に伴うUIの効率的な更新の詳細を処理するため、自分でコードを作成する必要はありません。
ReduxをReactで使用する
公式のReact-Redux UIバインディングライブラリは、Reduxコアとは別のパッケージです。追加でインストールする必要があります。
npm install react-redux
このチュートリアルでは、ReactとReduxを一緒に使用するために必要な最も重要なパターンと例を説明し、Todoアプリの一部として実際にどのように機能するかを見ていきます。
ReduxとReactを一緒に使用する方法の完全なガイドと、React-Redux APIのリファレンスドキュメントについては、公式のReact-Reduxドキュメント(https://react-redux.dokyumento.jp)をご覧ください。
コンポーネントツリーの設計
要件に基づいてステート構造を設計したように、UIコンポーネントの全体的なセットと、それらがアプリケーション内で互いにどのように関連するかを設計することもできます。
アプリのビジネス要件のリストに基づくと、少なくとも次のコンポーネントのセットが必要になります。
<App>
:他のすべてをレンダリングするルートコンポーネント。<Header>
:「新しいTodo」テキスト入力と「すべてのTodoを完了」チェックボックスが含まれています。<TodoList>
:フィルターされた結果に基づいて、現在表示されているすべてのTodo項目のリスト。<TodoListItem>
:単一のTodoリスト項目。Todoの完了ステータスを切り替えるためにクリックできるチェックボックスと、色のカテゴリセレクターがあります。
<Footer>
:アクティブなTodoの数と、完了ステータスと色のカテゴリに基づいてリストをフィルタリングするためのコントロールが表示されます。
この基本的なコンポーネント構造を超えて、コンポーネントをいくつかの異なる方法で分割することができます。たとえば、<Footer>
コンポーネントは、1つの大きなコンポーネントにすることも、<CompletedTodos>
、<StatusFilter>
、<ColorFilters>
のような複数の小さなコンポーネントを内部に持つこともできます。これらの分割方法に唯一の正解はなく、状況に応じて大きなコンポーネントを作成したり、多くの小さなコンポーネントに分割したりする方が良い場合があることがわかります。
今のところ、物事を簡単に追跡できるように、この小さなコンポーネントリストから始めます。なお、すでにReactを知っていることを前提としているため、これらのコンポーネントのレイアウトコードの作成方法の詳細をスキップして、ReactコンポーネントでReact-Reduxライブラリを実際に使用する方法に焦点を当てます。
Redux関連のロジックを追加する前の、このアプリの初期のReact UIを次に示します。
useSelector
を使用したストアからの状態の読み取り
Todo項目のリストを表示できるようにする必要があることはわかっています。まず、ストアからTodoのリストを読み取り、それらをループ処理し、Todoエントリごとに1つの<TodoListItem>
コンポーネントを表示できる<TodoList>
コンポーネントを作成することから始めましょう。
ReactのuseState
のようなフックには慣れているはずです。これらのフックはReactの関数コンポーネントで呼び出すことができ、Reactステート値にアクセスできるようになります。Reactでは、カスタムフックを作成することもでき、これによって、再利用可能なフックを抽出して、Reactの組み込みフックの上に独自の動作を追加できます。
他の多くのライブラリと同様に、React-Reduxには、独自のコンポーネントで使用できる独自のカスタムフックが含まれています。React-Reduxフックを使用すると、Reactコンポーネントがステートを読み取ったり、アクションをディスパッチしたりすることで、Reduxストアと通信できるようになります。
最初に注目するのは、useSelector
フックです。このフックを使用すると、ReactコンポーネントがReduxストアからデータを読み取れるようになります。
useSelector
は、セレクター関数と呼ばれる単一の関数を受け取ります。セレクターは、Redux ストアの状態全体を引数として受け取り、その状態から何らかの値を読み取り、その結果を返す関数です。
たとえば、私たちの Todo アプリの Redux の状態が、Todo アイテムの配列を state.todos
として保持していることを知っています。その Todo 配列を返す小さなセレクター関数を書くことができます。
const selectTodos = state => state.todos
または、現在「完了」とマークされている Todo がいくつあるかを知りたいかもしれません。
const selectTotalCompletedTodos = state => {
const completedTodos = state.todos.filter(todo => todo.completed)
return completedTodos.length
}
したがって、セレクターは Redux ストアの状態から値を返すことができ、その状態に基づいて派生した値を返すこともできます。
Todo の配列を <TodoList>
コンポーネントに読み込んでみましょう。まず、react-redux
ライブラリから useSelector
フックをインポートし、その引数としてセレクター関数を渡して呼び出します。
import React from 'react'
import { useSelector } from 'react-redux'
import TodoListItem from './TodoListItem'
const selectTodos = state => state.todos
const TodoList = () => {
const todos = useSelector(selectTodos)
// since `todos` is an array, we can loop over it
const renderedListItems = todos.map(todo => {
return <TodoListItem key={todo.id} todo={todo} />
})
return <ul className="todo-list">{renderedListItems}</ul>
}
export default TodoList
<TodoList>
コンポーネントが最初にレンダリングされるとき、useSelector
フックは selectTodos
を呼び出し、すべての Redux 状態オブジェクトを渡します。セレクターが返すものが何であれ、フックによってコンポーネントに返されます。したがって、コンポーネント内の const todos
は、Redux ストアの状態内にある同じ state.todos
配列を保持することになります。
しかし、{type: 'todos/todoAdded'}
のようなアクションをディスパッチした場合はどうなるでしょうか?Redux の状態はリデューサーによって更新されますが、コンポーネントは何か変更があったことを認識して、新しい Todo のリストで再レンダリングする必要があります。
store.subscribe()
を呼び出してストアの変更をリッスンできることはわかっているので、すべてのコンポーネントでストアをサブスクライブするコードを記述しようとすることができます。しかし、それはすぐに非常に反復的になり、処理が難しくなります。
幸いなことに、useSelector
は自動的に Redux ストアをサブスクライブしてくれます! そのため、アクションがディスパッチされるたびに、すぐにセレクター関数を再び呼び出します。セレクターによって返される値が最後に実行されたときから変更された場合、useSelector
はコンポーネントを強制的に新しいデータで再レンダリングします。コンポーネントで useSelector()
を一度呼び出すだけで、残りの作業はすべて行われます。
ただし、ここで非常に重要なことを覚えておく必要があります。
useSelector
は、厳密な ===
参照比較を使用して結果を比較するため、セレクターの結果が新しい参照である場合は常にコンポーネントが再レンダリングされます! つまり、セレクターで新しい参照を作成して返すと、データが実際には異ならない場合でも、アクションがディスパッチされるたびにコンポーネントが再レンダリングされる可能性があります。
たとえば、このセレクターを useSelector
に渡すと、コンポーネントは常に再レンダリングされます。これは、array.map()
が常に新しい配列参照を返すためです。
// Bad: always returning a new reference
const selectTodoDescriptions = state => {
// This creates a new array reference!
return state.todos.map(todo => todo.text)
}
この問題を修正する1つの方法については、このセクションの後半で説明します。「メモ化」されたセレクター関数を使用してパフォーマンスを向上させ、不要な再レンダリングを回避する方法についても、パート7:標準的なReduxパターンで説明します。
また、セレクター関数を別の変数として記述する必要はないことにも注意してください。次のように、useSelector
の呼び出しの中に直接セレクター関数を記述できます。
const todos = useSelector(state => state.todos)
useDispatch
を使用したアクションのディスパッチ
これで、Redux ストアからコンポーネントにデータを読み込む方法がわかりました。しかし、コンポーネントからストアにアクションをディスパッチするにはどうすればよいでしょうか?React の外部では、store.dispatch(action)
を呼び出すことができることは知っています。コンポーネントファイルではストアにアクセスできないため、コンポーネント内で dispatch
関数自体にアクセスする方法が必要です。
React-Redux の useDispatch
フック は、ストアの dispatch
メソッドを結果として提供します。(実際、フックの実装は本当に return store.dispatch
です。)
したがって、アクションをディスパッチする必要がある任意のコンポーネントで const dispatch = useDispatch()
を呼び出し、必要に応じて dispatch(someAction)
を呼び出すことができます。
<Header>
コンポーネントでそれを試してみましょう。ユーザーが新しい Todo アイテムのテキストを入力できるようにし、そのテキストを含む {type: 'todos/todoAdded'}
アクションをディスパッチする必要があることはわかっています。
「制御された入力」 を使用してユーザーがフォームテキストを入力できるようにする典型的な React フォームコンポーネントを作成します。次に、ユーザーが Enter キーを具体的に押したときに、そのアクションをディスパッチします。
import React, { useState } from 'react'
import { useDispatch } from 'react-redux'
const Header = () => {
const [text, setText] = useState('')
const dispatch = useDispatch()
const handleChange = e => setText(e.target.value)
const handleKeyDown = e => {
const trimmedText = e.target.value.trim()
// If the user pressed the Enter key:
if (e.key === 'Enter' && trimmedText) {
// Dispatch the "todo added" action with this text
dispatch({ type: 'todos/todoAdded', payload: trimmedText })
// And clear out the text input
setText('')
}
}
return (
<input
type="text"
placeholder="What needs to be done?"
autoFocus={true}
value={text}
onChange={handleChange}
onKeyDown={handleKeyDown}
/>
)
}
export default Header
Provider
によるストアの受け渡し
これで、コンポーネントはストアから状態を読み取り、ストアにアクションをディスパッチできるようになりました。ただし、まだ何か欠けています。React-Redux フックは、適切な Redux ストアをどこでどのように見つけているのでしょうか?フックは JS 関数であるため、store.js
からストアを自動的にインポートすることはできません。
代わりに、コンポーネントで使用するストアを React-Redux に具体的に伝える必要があります。これを行うには、<App>
全体の上に <Provider>
コンポーネントをレンダリングし、Redux ストアを <Provider>
に props として渡します。これを一度行うと、アプリケーション内のすべてのコンポーネントは、必要に応じて Redux ストアにアクセスできるようになります。
それをメインの index.js
ファイルに追加しましょう。
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import App from './App'
import store from './store'
ReactDOM.render(
// Render a `<Provider>` around the entire `<App>`,
// and pass the Redux store to it as a prop
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root')
)
これで、React で React-Redux を使用するための主要な部分を網羅しました。
useSelector
フックを呼び出して、React コンポーネントでデータを読み取ります。useDispatch
フックを呼び出して、React コンポーネントでアクションをディスパッチします。- 他のコンポーネントがストアと通信できるように、
<Provider store={store}>
を<App>
コンポーネント全体に配置します。
これで、アプリを実際に操作できるようになるはずです。これまでのところ、動作する UI は次のとおりです。
では、Todo アプリでこれらを一緒に使用できる他のいくつかの方法を見てみましょう。
React-Redux パターン
グローバル状態、コンポーネント状態、およびフォーム
ここまでで、「アプリのすべての状態を常に Redux ストアに入れる必要があるのか?」と疑問に思っているかもしれません。
答えはいいえです。アプリ全体で必要なグローバル状態は Redux ストアに入れる必要があります。1か所でのみ必要な状態は、コンポーネントの状態に保持する必要があります。
この良い例が、以前に作成した <Header>
コンポーネントです。入力の onChange
ハンドラーでアクションをディスパッチし、リデューサーに保持することで、Redux ストアに現在のテキスト入力文字列を保持することはできます。しかし、それは私たちに何のメリットも与えません。そのテキスト文字列が使用される場所は、ここ、<Header>
コンポーネントだけです。
したがって、その値を <Header>
コンポーネントの useState
フックに保持するのが理にかなっています。
同様に、isDropdownOpen
というブール値フラグがある場合、アプリ内の他のコンポーネントはそれを気にする必要はありません。実際には、このコンポーネントにローカルに保持する必要があります。
React + Redux アプリでは、グローバル状態は Redux ストアに格納し、ローカル状態は React コンポーネントに保持する必要があります。
どこに何を配置するか不明な場合は、Redux に入れる必要があるデータの種類を決定するための一般的な経験則を以下に示します。
- アプリケーションの他の部分はこのデータを気にしますか?
- この元のデータに基づいて、さらに派生したデータを作成できる必要がありますか?
- 複数のコンポーネントを駆動するために同じデータが使用されていますか?
- この状態を特定の時点に復元できること (つまり、タイムトラベルデバッグ) に価値はありますか?
- データをキャッシュしたいですか (つまり、既に存在する場合は、再度リクエストする代わりに状態にあるものを使用しますか)?
- UI コンポーネントのホットリロード (スワップ時に内部状態が失われる可能性がある) 中にこのデータの一貫性を維持したいですか?
これは、Redux でのフォームの考え方に関する一般的な良い例でもあります。ほとんどのフォームの状態は、おそらく Redux に保持しないでください。 代わりに、編集中はデータをフォームコンポーネントに保持し、ユーザーが完了したら Redux アクションをディスパッチしてストアを更新します。
コンポーネントでの複数のセレクターの使用
現在、<TodoList>
コンポーネントのみがストアからデータを読み取っています。<Footer>
コンポーネントもいくつかのデータを読み取り始めるのがどのようなものかを見てみましょう。
<Footer>
は、次の3つの異なる情報を知る必要があります。
- 完了した Todo の数
- 現在の「ステータス」フィルター値
- 選択された「色」カテゴリフィルターの現在のリスト
これらの値をコンポーネントに読み込むにはどうすればよいでしょうか?
1つのコンポーネント内で useSelector
を複数回呼び出すことができます。実際、これは良い考えです。useSelector
の各呼び出しは、常に可能な限り最小限の状態を返す必要があります。
以前に完了した Todo をカウントするセレクターを記述する方法を既に見てきました。フィルター値の場合、ステータスフィルター値とカラーフィルター値の両方が state.filters
スライスに存在します。このコンポーネントは両方を必要とするため、state.filters
オブジェクト全体を選択できます。
前述のように、すべての入力処理を <Footer>
に直接配置することも、<StatusFilter>
などの個別のコンポーネントに分割することもできます。この説明を短くするために、入力処理の正確な詳細についてはスキップし、いくつかのデータと変更ハンドラーコールバックが props として渡されるより小さな個別のコンポーネントがあると仮定します。
その仮定を前提とすると、コンポーネントの React-Redux 部分は次のようになる可能性があります。
import React from 'react'
import { useSelector } from 'react-redux'
import { availableColors, capitalize } from '../filters/colors'
import { StatusFilters } from '../filters/filtersSlice'
// Omit other footer components
const Footer = () => {
const todosRemaining = useSelector(state => {
const uncompletedTodos = state.todos.filter(todo => !todo.completed)
return uncompletedTodos.length
})
const { status, colors } = useSelector(state => state.filters)
// omit placeholder change handlers
return (
<footer className="footer">
<div className="actions">
<h5>Actions</h5>
<button className="button">Mark All Completed</button>
<button className="button">Clear Completed</button>
</div>
<RemainingTodos count={todosRemaining} />
<StatusFilter value={status} onChange={onStatusChange} />
<ColorFilters value={colors} onChange={onColorChange} />
</footer>
)
}
export default Footer
ID によるリスト項目のデータの選択
現在、<TodoList>
は state.todos
配列全体を読み取り、実際の Todo オブジェクトを props として各 <TodoListItem>
コンポーネントに渡しています。
これは機能しますが、パフォーマンス上の問題が発生する可能性があります。
- 1つの Todo オブジェクトを変更すると、Todo と
state.todos
配列の両方のコピーが作成され、各コピーはメモリ内の新しい参照になります。 useSelector
は、結果として新しい参照を検出すると、コンポーネントを強制的に再レンダリングします。- したがって、1つの Todo オブジェクトが更新された場合 (完了ステータスを切り替えるためにクリックするなど)、
<TodoList>
親コンポーネント全体が再レンダリングされます。 - 次に、React はデフォルトですべての子コンポーネントを再帰的に再レンダリングするため、実際には何も変更されていないほとんどの
<TodoListItem>
コンポーネントもすべて再レンダリングされます!
コンポーネントの再レンダリングは悪いことではありません。それが React が DOM を更新する必要があるかどうかを知る方法です。ただし、何も実際に変更されていないときに多数のコンポーネントを再レンダリングすると、リストが大きすぎる場合は、速度が低下する可能性があります。
これを修正する方法はいくつかあります。1つのオプションは、すべての<TodoListItem>
コンポーネントをReact.memo()
でラップすることです。これにより、propsが実際に変更された場合にのみ再レンダリングされるようになります。これはパフォーマンスを向上させるための良い選択肢であることが多いですが、子コンポーネントが何か本当に変更されるまで常に同じpropsを受け取る必要があります。各<TodoListItem>
コンポーネントはtodoアイテムをpropsとして受け取っているので、実際に変更されたpropsを受け取って再レンダリングする必要があるのはそのうちの1つだけです。
別のオプションとして、<TodoList>
コンポーネントがストアからtodo IDの配列のみを読み取り、それらのIDを子<TodoListItem>
コンポーネントにpropsとして渡すという方法もあります。その後、各<TodoListItem>
はそのIDを使用して、必要な正しいtodoオブジェクトを見つけることができます。
試してみましょう。
import React from 'react'
import { useSelector } from 'react-redux'
import TodoListItem from './TodoListItem'
const selectTodoIds = state => state.todos.map(todo => todo.id)
const TodoList = () => {
const todoIds = useSelector(selectTodoIds)
const renderedListItems = todoIds.map(todoId => {
return <TodoListItem key={todoId} id={todoId} />
})
return <ul className="todo-list">{renderedListItems}</ul>
}
今回は、<TodoList>
でストアからtodo IDの配列のみを選択し、各todoId
を子<TodoListItem>
のid
propとして渡します。
次に、<TodoListItem>
で、そのID値を使用してtodoアイテムを読み取ることができます。また、<TodoListItem>
を更新して、todoのIDに基づいて"toggled"アクションをディスパッチすることもできます。
import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { availableColors, capitalize } from '../filters/colors'
const selectTodoById = (state, todoId) => {
return state.todos.find(todo => todo.id === todoId)
}
// Destructure `props.id`, since we only need the ID value
const TodoListItem = ({ id }) => {
// Call our `selectTodoById` with the state _and_ the ID value
const todo = useSelector(state => selectTodoById(state, id))
const { text, completed, color } = todo
const dispatch = useDispatch()
const handleCompletedChanged = () => {
dispatch({ type: 'todos/todoToggled', payload: todo.id })
}
// omit other change handlers
// omit other list item rendering logic and contents
return (
<li>
<div className="view">{/* omit other rendering output */}</div>
</li>
)
}
export default TodoListItem
しかし、これには問題があります。以前に、セレクターで新しい配列参照を返すたびにコンポーネントが再レンダリングされると述べましたが、現在<TodoList>
で新しいID配列を返しています。この場合、todoを切り替える場合、ID配列の内容は同じであるはずです。なぜなら、同じtodoアイテムを表示しているからです。何も追加または削除していません。しかし、それらのIDを含む配列は新しい参照であるため、<TodoList>
は実際には必要ないときに再レンダリングされます。
この問題を解決する1つの可能な方法は、useSelector
が値が変更されたかどうかを判断するためにどのように値を比較するかを変更することです。useSelector
は、2番目の引数として比較関数を受け取ることができます。比較関数は古い値と新しい値で呼び出され、それらが同じと見なされる場合はtrue
を返します。同じである場合、useSelector
はコンポーネントを再レンダリングしません。
React-Reduxには、配列内のアイテムがまだ同じかどうかを確認するために使用できるshallowEqual
比較関数があります。それを試してみましょう。
import React from 'react'
import { useSelector, shallowEqual } from 'react-redux'
import TodoListItem from './TodoListItem'
const selectTodoIds = state => state.todos.map(todo => todo.id)
const TodoList = () => {
const todoIds = useSelector(selectTodoIds, shallowEqual)
const renderedListItems = todoIds.map(todoId => {
return <TodoListItem key={todoId} id={todoId} />
})
return <ul className="todo-list">{renderedListItems}</ul>
}
これで、todoアイテムを切り替えると、IDのリストは同じと見なされ、<TodoList>
は再レンダリングする必要がなくなります。1つの<TodoListItem>
は更新されたtodoオブジェクトを取得して再レンダリングされますが、残りのすべては既存のtodoオブジェクトを保持し、まったく再レンダリングする必要がなくなります。
前述のように、コンポーネントのレンダリングを改善するために、「メモ化されたセレクター」と呼ばれる特殊なセレクター関数を使用することもできます。それらの使用方法については別のセクションで説明します。
学んだこと
これで、動作するtodoアプリができました!アプリはストアを作成し、<Provider>
を使用してストアをReact UIレイヤーに渡し、useSelector
とuseDispatch
を呼び出してReactコンポーネントでストアと通信します。
残りの不足しているUI機能を自分で実装してみてください!追加する必要があるもののリストを次に示します。
<TodoListItem>
コンポーネントで、useDispatch
フックを使用して、色のカテゴリの変更とtodoの削除のアクションをディスパッチします。<Footer>
で、useDispatch
フックを使用して、すべてのtodoを完了としてマークする、完了したtodoをクリアする、およびフィルター値を変更するアクションをディスパッチします。
フィルターの実装については、パート7:標準的なReduxパターンで説明します。
この説明を短くするためにスキップしたコンポーネントとセクションを含め、アプリがどのように見えるかを見てみましょう。
- Reduxストアは、任意のUIレイヤーで使用できます。
- UIコードは常にストアをサブスクライブし、最新の状態を取得して、自身を再描画します。
- React-Reduxは、React用の公式Redux UIバインディングライブラリです。
- React-Reduxは、別の
react-redux
パッケージとしてインストールされます。
- React-Reduxは、別の
useSelector
フックを使用すると、Reactコンポーネントがストアからデータを読み取ることができます。- セレクター関数は、ストア全体の
state
を引数として受け取り、その状態に基づいて値を返します。 useSelector
はセレクター関数を呼び出し、セレクターからの結果を返します。useSelector
はストアをサブスクライブし、アクションがディスパッチされるたびにセレクターを再実行します。- セレクターの結果が変更されると、
useSelector
は新しいデータでコンポーネントを再レンダリングします。
- セレクター関数は、ストア全体の
useDispatch
フックを使用すると、Reactコンポーネントがストアにアクションをディスパッチできます。useDispatch
は実際のstore.dispatch
関数を返します。- コンポーネント内で必要に応じて
dispatch(action)
を呼び出すことができます。
<Provider>
コンポーネントは、ストアを他のReactコンポーネントで使用できるようにします。<App>
全体を<Provider store={store}>
で囲んでレンダリングします。
次は?
UIが動作するようになったので、Reduxアプリをサーバーと通信させる方法を見てみましょう。パート6:非同期ロジックでは、タイムアウトやAJAX呼び出しなどの非同期ロジックがReduxデータフローにどのように適合するかについて説明します。