Connect: mapDispatchToProps
によるアクションのディスパッチ
connect
に渡される第 2 引数として、mapDispatchToProps
はストアにアクションをディスパッチするために使用されます。
dispatch
は Redux ストアの関数です。アクションをディスパッチするには、store.dispatch
を呼び出します。これが状態の変化をトリガーする唯一の方法です。
React Redux では、コンポーネントがストアに直接アクセスすることはありません。connect
が代わりにそれを行います。React Redux は、コンポーネントがアクションをディスパッチするための 2 つの方法を提供します。
- デフォルトでは、接続されたコンポーネントは
props.dispatch
を受け取り、自身でアクションをディスパッチできます。 connect
はmapDispatchToProps
という引数を受け入れることができます。これにより、呼び出されたときにディスパッチする関数を作成し、それらの関数を props としてコンポーネントに渡すことができます。
mapDispatchToProps
関数は通常、略して mapDispatch
と呼ばれますが、実際に使用される変数名は任意です。
ディスパッチのアプローチ
デフォルト: Prop としての dispatch
connect()
に第 2 引数を指定しない場合、コンポーネントはデフォルトで dispatch
を受け取ります。例:
connect()(MyComponent)
// which is equivalent with
connect(null, null)(MyComponent)
// or
connect(mapStateToProps /** no second argument */)(MyComponent)
このようにしてコンポーネントを接続すると、コンポーネントは props.dispatch
を受け取ります。これを使用して、ストアにアクションをディスパッチできます。
function Counter({ count, dispatch }) {
return (
<div>
<button onClick={() => dispatch({ type: 'DECREMENT' })}>-</button>
<span>{count}</span>
<button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button>
<button onClick={() => dispatch({ type: 'RESET' })}>reset</button>
</div>
)
}
mapDispatchToProps
パラメータの提供
mapDispatchToProps
を提供すると、コンポーネントがディスパッチする必要がある可能性のあるアクションを指定できます。アクションディスパッチ関数を props として提供できます。したがって、props.dispatch(() => increment())
を呼び出す代わりに、直接 props.increment()
を呼び出すことができます。それを行う理由がいくつかあります。
より宣言的
まず、ディスパッチロジックを関数にカプセル化すると、実装がより宣言的になります。アクションをディスパッチし、Redux ストアにデータフローを処理させることは、動作を 実装する方法 であり、何を行うか ではありません。
良い例としては、ボタンがクリックされたときにアクションをディスパッチする場合が挙げられます。ボタンを直接接続することは概念的に意味をなさない可能性が高く、ボタンが dispatch
を参照することもありません。
// button needs to be aware of "dispatch"
<button onClick={() => dispatch({ type: "SOMETHING" })} />
// button unaware of "dispatch",
<button onClick={doSomething} />
すべてのアクションクリエイターをアクションをディスパッチする関数でラップしたら、コンポーネントは dispatch
を必要としなくなります。したがって、独自の mapDispatchToProps
を定義した場合、接続されたコンポーネントは dispatch
を受け取らなくなります。**
アクションディスパッチロジックを (未接続の) 子コンポーネントに渡す
さらに、アクションディスパッチ関数を子 (おそらく未接続の) コンポーネントに渡すこともできます。これにより、より多くのコンポーネントがアクションをディスパッチできるようになり、Redux を「認識」させないようにできます。
// pass down toggleTodo to child component
// making Todo able to dispatch the toggleTodo action
const TodoList = ({ todos, toggleTodo }) => (
<div>
{todos.map((todo) => (
<Todo todo={todo} onClick={toggleTodo} />
))}
</div>
)
これが React Redux の connect
が行うことです。Redux ストアとやり取りするロジックをカプセル化し、心配する必要がないようにします。そして、これは実装で最大限に活用する必要があるものです。
mapDispatchToProps
の 2 つの形式
mapDispatchToProps
パラメータには、2 つの形式があります。関数形式ではより多くのカスタマイズが可能ですが、オブジェクト形式は使いやすいです。
- 関数形式: より多くのカスタマイズが可能で、
dispatch
とオプションでownProps
にアクセスできます。 - オブジェクトショートハンド形式: より宣言的で使いやすい。
⭐ 注: 何らかの方法でディスパッチ動作を具体的にカスタマイズする必要がない限り、
mapDispatchToProps
のオブジェクト形式を使用することをお勧めします。
関数としての mapDispatchToProps
の定義
mapDispatchToProps
を関数として定義すると、コンポーネントが受け取る関数と、それらがアクションをディスパッチする方法をカスタマイズする上で、最大限の柔軟性が得られます。dispatch
と ownProps
にアクセスできます。この機会を利用して、接続されたコンポーネントが呼び出すカスタム関数を作成できます。
引数
dispatch
ownProps
(オプション)
dispatch
mapDispatchToProps
関数は、最初の引数として dispatch
を使用して呼び出されます。通常、この関数を利用して、内部で dispatch()
を呼び出す新しい関数を返し、プレーンなアクションオブジェクトを直接渡すか、アクションクリエイターの結果を渡します。
const mapDispatchToProps = (dispatch) => {
return {
// dispatching plain actions
increment: () => dispatch({ type: 'INCREMENT' }),
decrement: () => dispatch({ type: 'DECREMENT' }),
reset: () => dispatch({ type: 'RESET' }),
}
}
また、アクションクリエイターに引数を転送することもできます。
const mapDispatchToProps = (dispatch) => {
return {
// explicitly forwarding arguments
onClick: (event) => dispatch(trackClick(event)),
// implicitly forwarding arguments
onReceiveImpressions: (...impressions) =>
dispatch(trackImpressions(impressions)),
}
}
ownProps
(オプション)
mapDispatchToProps
関数が 2 つのパラメータを取るように宣言されている場合、最初のパラメータとして dispatch
、2 番目のパラメータとして接続されたコンポーネントに渡された props
を使用して呼び出され、接続されたコンポーネントが新しい props を受け取るたびに再呼び出しされます。
これは、コンポーネントの再レンダリング時にアクションディスパッチャーに新しい props
を再バインドする代わりに、コンポーネントの props
が変更されたときに再バインドできることを意味します。
コンポーネントのマウント時にバインド
render() {
return <button onClick={() => this.props.toggleTodo(this.props.todoId)} />
}
const mapDispatchToProps = dispatch => {
return {
toggleTodo: todoId => dispatch(toggleTodo(todoId))
}
}
props
の変更時にバインド
render() {
return <button onClick={() => this.props.toggleTodo()} />
}
const mapDispatchToProps = (dispatch, ownProps) => {
return {
toggleTodo: () => dispatch(toggleTodo(ownProps.todoId))
}
}
戻り値
mapDispatchToProps
関数は、プレーンオブジェクトを返す必要があります。
- オブジェクトの各フィールドは、独自のコンポーネントの個別の prop になり、値は通常、呼び出されたときにアクションをディスパッチする関数である必要があります。
dispatch
内で (プレーンなオブジェクトアクションとは対照的に) アクションクリエイターを使用する場合、フィールドキーをアクションクリエイターと同じ名前で単純に付けるのが慣例です。
const increment = () => ({ type: 'INCREMENT' })
const decrement = () => ({ type: 'DECREMENT' })
const reset = () => ({ type: 'RESET' })
const mapDispatchToProps = (dispatch) => {
return {
// dispatching actions returned by action creators
increment: () => dispatch(increment()),
decrement: () => dispatch(decrement()),
reset: () => dispatch(reset()),
}
}
mapDispatchToProps
関数の戻り値は、props として接続されたコンポーネントにマージされます。それらを直接呼び出して、アクションをディスパッチできます。
function Counter({ count, increment, decrement, reset }) {
return (
<div>
<button onClick={decrement}>-</button>
<span>{count}</span>
<button onClick={increment}>+</button>
<button onClick={reset}>reset</button>
</div>
)
}
(カウンターの例の完全なコードは、この CodeSandbox にあります。)
bindActionCreators
を使用した mapDispatchToProps
関数の定義
これらの関数を手動でラップするのは面倒なため、Redux はそれを簡略化する関数を提供しています。
bindActionCreators
は、値が アクションクリエイター であるオブジェクトを、同じキーを持つオブジェクトに変換しますが、すべてのアクションクリエイターがdispatch
呼び出しでラップされ、直接呼び出すことができます。bindActionCreators
に関する Redux ドキュメント を参照してください。
bindActionCreators
は 2 つのパラメータを受け入れます。
関数
(アクションクリエイター) またはオブジェクト
(各フィールドはアクションクリエイター)dispatch
bindActionCreators
によって生成されたラッパー関数は、すべての引数を自動的に転送するため、手動で行う必要はありません。
import { bindActionCreators } from 'redux'
const increment = () => ({ type: 'INCREMENT' })
const decrement = () => ({ type: 'DECREMENT' })
const reset = () => ({ type: 'RESET' })
// binding an action creator
// returns (...args) => dispatch(increment(...args))
const boundIncrement = bindActionCreators(increment, dispatch)
// binding an object full of action creators
const boundActionCreators = bindActionCreators(
{ increment, decrement, reset },
dispatch,
)
// returns
// {
// increment: (...args) => dispatch(increment(...args)),
// decrement: (...args) => dispatch(decrement(...args)),
// reset: (...args) => dispatch(reset(...args)),
// }
mapDispatchToProps
関数で bindActionCreators
を使用するには
import { bindActionCreators } from 'redux'
// ...
function mapDispatchToProps(dispatch) {
return bindActionCreators({ increment, decrement, reset }, dispatch)
}
// component receives props.increment, props.decrement, props.reset
connect(null, mapDispatchToProps)(Counter)
dispatch
の手動挿入
mapDispatchToProps
引数が指定されている場合、コンポーネントはデフォルトの dispatch
を受け取らなくなります。mapDispatchToProps
の戻り値に手動で追加して戻すことができますが、ほとんどの場合、これを行う必要はありません。
import { bindActionCreators } from 'redux'
// ...
function mapDispatchToProps(dispatch) {
return {
dispatch,
...bindActionCreators({ increment, decrement, reset }, dispatch),
}
}
オブジェクトとしての mapDispatchToProps
の定義
React コンポーネントで Redux アクションをディスパッチするためのセットアップは、非常に似たプロセスに従っていることを確認しました。アクションクリエイターを定義し、(…args) => dispatch(actionCreator(…args))
のような別の関数でラップし、そのラッパー関数を props としてコンポーネントに渡します。
これが非常に一般的なため、connect
は mapDispatchToProps
引数に対して「オブジェクトショートハンド」形式をサポートしています。関数ではなくアクションクリエイターでいっぱいのオブジェクトを渡すと、connect
は内部で自動的に bindActionCreators
を呼び出します。
ディスパッチの動作をカスタマイズする特定の理由がない限り、常に mapDispatchToProps
の「オブジェクトショートハンド」形式を使用することをお勧めします。
注意点として、
mapDispatchToProps
オブジェクトの各フィールドは、アクションクリエイターであるとみなされます。- コンポーネントは
dispatch
を props として受け取らなくなります。
// React Redux does this for you automatically:
;(dispatch) => bindActionCreators(mapDispatchToProps, dispatch)
したがって、mapDispatchToProps
は単純に次のようになります。
const mapDispatchToProps = {
increment,
decrement,
reset,
}
変数の実際の名前は任意なので、actionCreators
のような名前を付けたり、connect
の呼び出しでオブジェクトをインラインで定義したりすることもできます。
import { increment, decrement, reset } from './counterActions'
const actionCreators = {
increment,
decrement,
reset,
}
export default connect(mapState, actionCreators)(Counter)
// or
export default connect(mapState, { increment, decrement, reset })(Counter)
よくある問題
なぜコンポーネントが dispatch
を受け取らないのですか?
別名として
TypeError: this.props.dispatch is not a function
これは、this.props.dispatch
を呼び出そうとしたときに発生する一般的なエラーですが、dispatch
はコンポーネントに注入されていません。
dispatch
がコンポーネントに注入されるのは、以下の場合のみです。
1. mapDispatchToProps
を提供しない場合
デフォルトの mapDispatchToProps
は単に dispatch => ({ dispatch })
です。mapDispatchToProps
を提供しない場合、上記のように dispatch
が提供されます。
別の言い方をすれば、以下のような場合です。
// component receives `dispatch`
connect(mapStateToProps /** no second argument*/)(Component)
2. カスタマイズされた mapDispatchToProps
関数が、明示的に dispatch
を返す場合
カスタマイズされた mapDispatchToProps
関数を提供することで、dispatch
を復活させることができます。
const mapDispatchToProps = (dispatch) => {
return {
increment: () => dispatch(increment()),
decrement: () => dispatch(decrement()),
reset: () => dispatch(reset()),
dispatch,
}
}
または、bindActionCreators
を使用する場合。
import { bindActionCreators } from 'redux'
function mapDispatchToProps(dispatch) {
return {
dispatch,
...bindActionCreators({ increment, decrement, reset }, dispatch),
}
}
Redux の GitHub issue #255 でこのエラーが実際に発生しているのをご覧ください。
mapDispatchToProps
を指定した場合に、コンポーネントに dispatch
を提供するかどうかについての議論があります ( #255に対するDan Abramovの回答 )。現在の実装意図をさらに理解するために、それらを読むことができます。
Reduxで mapStateToProps
なしで mapDispatchToProps
を使用できますか?
はい。最初のパラメーターを undefined
または null
を渡すことでスキップできます。コンポーネントはストアをサブスクライブしませんが、mapDispatchToProps
によって定義された dispatch props は引き続き受け取ります。
connect(null, mapDispatchToProps)(MyComponent)
store.dispatch
を呼び出すことはできますか?
Reactコンポーネントでストアを直接操作するのはアンチパターンです。ストアを明示的にインポートする場合でも、コンテキスト経由でアクセスする場合でも同様です(詳細については、ストア設定に関するRedux FAQエントリを参照してください)。React Reduxの connect
にストアへのアクセスを処理させ、propsに渡される dispatch
を使用してアクションをディスパッチします。
リンクと参考文献
チュートリアル
関連ドキュメント
Q&A