チュートリアル: connect
API の使用
現在、React-Redux hooks API をデフォルトとして使用することをお勧めします。ただし、connect
API も問題なく動作します。
このチュートリアルでは、Redux ロジックをタイプ別にフォルダに分けるなど、現在では推奨されていない古いプラクティスも示しています。このチュートリアルは、完全を期すために現状維持していますが、現在のベストプラクティスについては、Redux ドキュメントの「Redux Essentials」チュートリアルと、Redux スタイルガイドを参照することをお勧めします。
現在、hooks API を紹介する新しいチュートリアルを作成中です。それまでの間は、Redux Fundamentals, Part 5: UI and Reactで hooks のチュートリアルを読むことをお勧めします。
React Redux の実践的な使い方を見るために、Todo リストアプリを作成するステップバイステップの例を示します。
Todo リストの例
ジャンプ先
- 🤞 コードだけを見たい
- 👆 ストアの提供
- ✌️ コンポーネントの接続
React UI コンポーネント
React UI コンポーネントは次のように実装しました
TodoApp
はアプリのエントリーコンポーネントです。ヘッダー、AddTodo
、TodoList
、VisibilityFilters
コンポーネントをレンダリングします。AddTodo
は、ユーザーが Todo アイテムを入力し、「Todo を追加」ボタンをクリックするとリストに追加できるコンポーネントですonChange
で状態を設定する制御された入力を使用します。- ユーザーが「Todo を追加」ボタンをクリックすると、Todo をストアに追加するためのアクション (React Redux を使用して提供します) をディスパッチします。
TodoList
は Todo のリストをレンダリングするコンポーネントですVisibilityFilters
のいずれかが選択されると、フィルタリングされた Todo のリストをレンダリングします。
Todo
は単一の Todo アイテムをレンダリングするコンポーネントです- Todo の内容をレンダリングし、Todo が完了したことを取り消し線で示します。
onClick
で Todo の完了ステータスを切り替えるアクションをディスパッチします。
VisibilityFilters
は、すべて、完了、未完了 のシンプルなフィルターセットをレンダリングします。それぞれのフィルターをクリックすると、Todo がフィルタリングされます- 親から、ユーザーが現在選択しているフィルターを示す
activeFilter
プロパティを受け入れます。アクティブなフィルターは下線付きでレンダリングされます。 - 選択したフィルターを更新するために
setFilter
アクションをディスパッチします。
- 親から、ユーザーが現在選択しているフィルターを示す
constants
にはアプリの定数データが保持されます。- 最後に、
index
はアプリを DOM にレンダリングします。
Redux ストア
アプリケーションの Redux 部分は、Redux ドキュメントで推奨されているパターンを使用して設定されています
- ストア
todos
: 正規化された Todo のリデューサー。すべての Todo のbyIds
マップと、すべての ID のリストを含むallIds
を含みます。visibilityFilters
: 単純な文字列all
、completed
、またはincomplete
。
- アクションクリエーター
addTodo
は Todo を追加するアクションを作成します。単一の文字列変数content
を取り、自己インクリメントされたid
とcontent
を含むpayload
でADD_TODO
アクションを返しますtoggleTodo
は Todo を切り替えるアクションを作成します。単一の数値変数id
を取り、id
のみを含むpayload
でTOGGLE_TODO
アクションを返しますsetFilter
はアプリのアクティブなフィルターを設定するアクションを作成します。単一の文字列変数filter
を取り、filter
自体を含むpayload
でSET_FILTER
アクションを返します
- リデューサー
todos
リデューサーADD_TODO
アクションを受け取ると、allIds
フィールドにid
を追加し、byIds
フィールド内に Todo を設定しますTOGGLE_TODO
アクションを受け取ると、Todo のcompleted
フィールドを切り替えます
visibilityFilters
リデューサーは、SET_FILTER
アクションペイロードから受け取った新しいフィルターにストアのスライスを設定します
- アクションタイプ
actionTypes.js
ファイルを使用して、再利用されるアクションタイプの定数を保持します
- セレクター
getTodoList
はtodos
ストアからallIds
リストを返しますgetTodoById
はid
で指定されたストア内の Todo を検索しますgetTodos
は少し複雑です。allIds
からすべてのid
を取得し、byIds
で各 Todo を検索し、Todo の最終的な配列を返しますgetTodosByVisibilityFilter
は可視性フィルターに従って Todo をフィルタリングします
UI コンポーネントと、上記の接続されていない Redux ストアのソースコードについては、この CodeSandbox をチェックしてください。
次に、React Redux を使用してこのストアをアプリに接続する方法を示します。
ストアの提供
まず、アプリで store
を利用できるようにする必要があります。これを行うには、React Redux が提供する <Provider />
API でアプリをラップします。
// index.js
import React from 'react'
import ReactDOM from 'react-dom'
import TodoApp from './TodoApp'
import { Provider } from 'react-redux'
import store from './redux/store'
// As of React 18
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
<Provider store={store}>
<TodoApp />
</Provider>,
)
<TodoApp />
が、prop として渡された store
を持つ <Provider />
でラップされていることに注意してください。
コンポーネントの接続
React Redux は、Redux ストアから値を読み取る (およびストアが更新されたときに値を再読み取りする) ための connect
関数を提供します。
connect
関数は、両方ともオプションである 2 つの引数を受け取ります
mapStateToProps
: ストアの状態が変更されるたびに呼び出されます。ストアの状態全体を受け取り、このコンポーネントが必要とするデータのオブジェクトを返す必要があります。mapDispatchToProps
: このパラメーターは、関数またはオブジェクトのいずれかになります。- 関数である場合、コンポーネントの作成時に一度呼び出されます。
dispatch
を引数として受け取り、アクションをディスパッチするためにdispatch
を使用する関数のオブジェクトを返す必要があります。 - アクションクリエーターのオブジェクトである場合、各アクションクリエーターは、呼び出されると自動的にアクションをディスパッチする prop 関数に変換されます。注: この「オブジェクトショートハンド」形式を使用することをお勧めします。
- 関数である場合、コンポーネントの作成時に一度呼び出されます。
通常、connect
は次のように呼び出します
const mapStateToProps = (state, ownProps) => ({
// ... computed data from state and optionally ownProps
})
const mapDispatchToProps = {
// ... normally is an object full of action creators
}
// `connect` returns a new function that accepts the component to wrap:
const connectToStore = connect(mapStateToProps, mapDispatchToProps)
// and that function returns the connected, wrapper component:
const ConnectedComponent = connectToStore(Component)
// We normally do both in one step, like this:
connect(mapStateToProps, mapDispatchToProps)(Component)
まず、<AddTodo />
から作業を始めましょう。新しい Todo を追加するために store
への変更をトリガーする必要があります。したがって、ストアにアクションを dispatch
できる必要があります。これがその方法です。
addTodo
アクションクリエーターは次のようになります
// redux/actions.js
import { ADD_TODO } from './actionTypes'
let nextTodoId = 0
export const addTodo = (content) => ({
type: ADD_TODO,
payload: {
id: ++nextTodoId,
content,
},
})
// ... other actions
connect
に渡すことで、コンポーネントはそれを prop として受け取り、呼び出されると自動的にアクションをディスパッチします。
// components/AddTodo.js
// ... other imports
import { connect } from 'react-redux'
import { addTodo } from '../redux/actions'
class AddTodo extends React.Component {
// ... component implementation
}
export default connect(null, { addTodo })(AddTodo)
<AddTodo />
が <Connect(AddTodo) />
という親コンポーネントでラップされていることに注意してください。一方、<AddTodo />
は 1 つの prop (addTodo
アクション) を取得します。
addTodo
アクションをディスパッチして入力をリセットするために、handleAddTodo
関数も実装する必要があります
// components/AddTodo.js
import React from 'react'
import { connect } from 'react-redux'
import { addTodo } from '../redux/actions'
class AddTodo extends React.Component {
// ...
handleAddTodo = () => {
// dispatches actions to add todo
this.props.addTodo(this.state.input)
// sets state back to empty string
this.setState({ input: '' })
}
render() {
return (
<div>
<input
onChange={(e) => this.updateInput(e.target.value)}
value={this.state.input}
/>
<button className="add-todo" onClick={this.handleAddTodo}>
Add Todo
</button>
</div>
)
}
}
export default connect(null, { addTodo })(AddTodo)
これで、<AddTodo />
がストアに接続されました。Todo を追加すると、ストアを変更するアクションがディスパッチされます。他のコンポーネントがまだ接続されていないため、アプリには表示されません。Redux DevTools Extension が接続されている場合は、アクションがディスパッチされていることがわかります
ストアもそれに応じて変更されていることがわかります
<TodoList />
コンポーネントは Todo のリストをレンダリングする役割を担っています。したがって、ストアからデータを読み取る必要があります。ストアから必要なデータの一部を記述する関数である mapStateToProps
パラメーターを指定して connect
を呼び出すことで、有効にします。
<Todo />
コンポーネントは、Todo アイテムを prop として受け取ります。これは todos
の byIds
フィールドからの情報です。ただし、どの Todo をどの順序でレンダリングする必要があるかを示すストアの allIds
フィールドからの情報も必要です。mapStateToProps
関数は次のようになる可能性があります
// components/TodoList.js
// ...other imports
import { connect } from "react-redux";
const TodoList = // ... UI component implementation
const mapStateToProps = state => {
const { byIds, allIds } = state.todos || {};
const todos =
allIds && allIds.length
? allIds.map(id => (byIds ? { ...byIds[id], id } : null))
: null;
return { todos };
};
export default connect(mapStateToProps)(TodoList);
幸いなことに、これを正確に行うセレクターがあります。セレクターをインポートしてここで使用するだけです。
// redux/selectors.js
export const getTodosState = (store) => store.todos
export const getTodoList = (store) =>
getTodosState(store) ? getTodosState(store).allIds : []
export const getTodoById = (store, id) =>
getTodosState(store) ? { ...getTodosState(store).byIds[id], id } : {}
export const getTodos = (store) =>
getTodoList(store).map((id) => getTodoById(store, id))
// components/TodoList.js
// ...other imports
import { connect } from "react-redux";
import { getTodos } from "../redux/selectors";
const TodoList = // ... UI component implementation
export default connect(state => ({ todos: getTodos(state) }))(TodoList);
データの複雑なルックアップや計算は、セレクター関数にカプセル化することをお勧めします。さらに、Reselect を使用して、不要な処理をスキップできる「メモ化」されたセレクターを記述することで、パフォーマンスを最適化できます。(なぜセレクター関数を使用するのか、またどのように使用するのかについての詳細は、派生データの計算に関するReduxドキュメントと、ブログ記事Idiomatic Redux: Using Reselect Selectors for Encapsulation and Performanceを参照してください。)
これで<TodoList />
がストアに接続されました。TODOリストを受け取り、それらをマッピングして、各TODOを<Todo />
コンポーネントに渡す必要があります。<Todo />
はそれらを画面にレンダリングします。TODOを追加してみてください。TODOリストに表示されるはずです!
さらに多くのコンポーネントを接続します。その前に、少し立ち止まって、まずconnect
について詳しく学びましょう。
connect
の一般的な呼び出し方
作業しているコンポーネントの種類に応じて、connect
の呼び出し方にはさまざまな方法があり、最も一般的なものを以下にまとめます。
ストアをサブスクライブしない | ストアをサブスクライブする | |
---|---|---|
アクションクリエーターを注入しない | connect()(Component) | connect(mapStateToProps)(Component) |
アクションクリエーターを注入する | connect(null, mapDispatchToProps)(Component) | connect(mapStateToProps, mapDispatchToProps)(Component) |
ストアをサブスクライブせず、アクションクリエーターを注入しない
引数を何も指定せずにconnect
を呼び出すと、コンポーネントは次のようになります。
- ストアが変更されても再レンダリングされません
- アクションを手動でディスパッチするために使用できる
props.dispatch
を受け取ります
// ... Component
export default connect()(Component) // Component will receive `dispatch` (just like our <TodoList />!)
ストアをサブスクライブし、アクションクリエーターを注入しない
mapStateToProps
のみでconnect
を呼び出すと、コンポーネントは次のようになります。
mapStateToProps
がストアから抽出した値にサブスクライブし、それらの値が変更された場合にのみ再レンダリングします- アクションを手動でディスパッチするために使用できる
props.dispatch
を受け取ります
// ... Component
const mapStateToProps = (state) => state.partOfState
export default connect(mapStateToProps)(Component)
ストアをサブスクライブせず、アクションクリエーターを注入する
mapDispatchToProps
のみでconnect
を呼び出すと、コンポーネントは次のようになります。
- ストアが変更されても再レンダリングされません
mapDispatchToProps
で注入した各アクションクリエーターをpropsとして受け取り、呼び出されると自動的にアクションをディスパッチします
import { addTodo } from './actionCreators'
// ... Component
export default connect(null, { addTodo })(Component)
ストアをサブスクライブし、アクションクリエーターを注入する
mapStateToProps
とmapDispatchToProps
の両方でconnect
を呼び出すと、コンポーネントは次のようになります。
mapStateToProps
がストアから抽出した値にサブスクライブし、それらの値が変更された場合にのみ再レンダリングしますmapDispatchToProps
で注入したすべてのアクションクリエーターをpropsとして受け取り、呼び出されると自動的にアクションをディスパッチします。
import * as actionCreators from './actionCreators'
// ... Component
const mapStateToProps = (state) => state.partOfState
export default connect(mapStateToProps, actionCreators)(Component)
これら4つのケースが、connect
の最も基本的な使用法を網羅しています。connect
の詳細については、より詳細に説明しているAPIセクションを引き続きお読みください。
それでは、残りの<TodoApp />
を接続しましょう。
TODOの切り替えの相互作用はどのように実装すればよいでしょうか?熱心な読者ならすでにお答えが出ているかもしれません。環境をセットアップし、ここまでの手順に従ってきた場合は、ここで一度手を止めて、自分で機能を実装してみるのも良いでしょう。<Todo />
を同様の方法で接続してtoggleTodo
をディスパッチしても驚くことではないでしょう。
// components/Todo.js
// ... other imports
import { connect } from "react-redux";
import { toggleTodo } from "../redux/actions";
const Todo = // ... component implementation
export default connect(
null,
{ toggleTodo }
)(Todo);
これでTODOの完了を切り替えることができます。もうすぐです!
最後に、VisibilityFilters
機能を実装しましょう。
<VisibilityFilters />
コンポーネントは、現在アクティブなフィルターをストアから読み取り、ストアにアクションをディスパッチできる必要があります。したがって、mapStateToProps
とmapDispatchToProps
の両方を渡す必要があります。ここのmapStateToProps
は、visibilityFilter
状態の単純なアクセサーにすることができます。また、mapDispatchToProps
には、setFilter
アクションクリエーターが含まれます。
// components/VisibilityFilters.js
// ... other imports
import { connect } from "react-redux";
import { setFilter } from "../redux/actions";
const VisibilityFilters = // ... component implementation
const mapStateToProps = state => {
return { activeFilter: state.visibilityFilter };
};
export default connect(
mapStateToProps,
{ setFilter }
)(VisibilityFilters);
一方、アクティブなフィルターに従ってTODOをフィルタリングするために、<TodoList />
コンポーネントも更新する必要があります。以前、<TodoList />
のconnect
関数呼び出しに渡したmapStateToProps
は、単にTODOリスト全体を選択するセレクターでした。TODOをステータスでフィルタリングするのに役立つ別のセレクターを記述しましょう。
// redux/selectors.js
// ... other selectors
export const getTodosByVisibilityFilter = (store, visibilityFilter) => {
const allTodos = getTodos(store)
switch (visibilityFilter) {
case VISIBILITY_FILTERS.COMPLETED:
return allTodos.filter((todo) => todo.completed)
case VISIBILITY_FILTERS.INCOMPLETE:
return allTodos.filter((todo) => !todo.completed)
case VISIBILITY_FILTERS.ALL:
default:
return allTodos
}
}
そして、セレクターの助けを借りてストアに接続します
// components/TodoList.js
// ...
const mapStateToProps = (state) => {
const { visibilityFilter } = state
const todos = getTodosByVisibilityFilter(state, visibilityFilter)
return { todos }
}
export default connect(mapStateToProps)(TodoList)
これで、React Reduxを使用したTODOアプリの非常に簡単な例が完成しました。すべてのコンポーネントが接続されました!素晴らしいでしょう?🎉🎊
リンク
さらにヘルプが必要な場合
- Reactiflux Reduxチャンネル
- StackOverflow
- GitHub Issues