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 にアクセスできます。この機会を利用して、接続されたコンポーネントが呼び出すカスタム関数を作成できます。
引数
dispatchownProps(オプション)
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