Connect:使用 mapDispatchToProps
Dispatch Action
作为传递给 connect
的第二个参数,mapDispatchToProps
用于 dispatch actions 给 store。
dispatch
是 Redux store 的一个函数。你可以调用 store.dispatch
来 dispatch 一个 action。
这是触发 state 变更的唯一方法。
使用 React Redux,你的组件永远不会直接访问 store —— connect
会为你完成这个过程。
React Redux 为你提供了两种让组件 dispatch actions 的方法:
- 默认情况下,连接的组件接收
props.dispatch
并可以自行 dispatch actions。 connect
可以接受一个名为mapDispatchToProps
的参数,它允许你创建在被调用时 dispatch 的函数,并将这些函数作为 props 传递给你的组件。
mapDispatchToProps
函数通常简称为 mapDispatch
,但实际使用的变量名可以是任何你想要的。
Dispatch 的方法
默认:dispatch
作为 props
如果你没有为 connect()
指定第二个参数,你的组件将默认接收 dispatch
。例如:
connect()(MyComponent);
// 等同于
connect(null, null)(MyComponent);
// 或者
connect(mapStateToProps /** 没有第二个参数 */)(MyComponent);
一旦你以这种方式连接了你的组件,你的组件就会接收到 props.dispatch
。你可以使用它向 store dispatch actions。
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
允许你指定组件可能需要 dispatch 的一些 action。它允许你提供 dispatch action 的函数作为 props。因此,你可以直接调用 props.increment()
,而不是调用 props.dispatch(() => increment())
。你想要这样做的原因有几个。
更具声明性
首先,将 dispatch 逻辑封装到函数中使得实现更具声明性。 dispatch action 并让 Redux store 处理数据流是 如何 实现行为,而不是它做了 什么。
一个很好的例子是在单击按钮时 dispatch 一个 action。直接连接按钮在概念上可能没有意义,按钮引用 dispatch
也没有意义。
// button 需要注意 "dispatch"
<button onClick={() => dispatch({ type: "SOMETHING" })} />
// button 不需要注意 "dispatch",
<button onClick={doSomething} />
一旦你用 dispatch actions 的函数包装了我们所有的 action creators,这个组件就不再需要 dispatch
了。
因此,如果你定义自己的 mapDispatchToProps
,连接的组件将不再接收 dispatch
。
将 dispatch action 的逻辑传递给(未连接的)子组件
此外,你还可以将 dispatch action 的函数传递给子(可能未连接)组件。 这允许更多组件 dispatch actions,同时让它们“不察觉” Redux。
// 将 toggleTodo 向下传递给子组件
// 使 Todo 能够 dispatch toggleTodo action
const TodoList = ({ todos, toggleTodo }) => (
<div>
{todos.map((todo) => (
<Todo todo={todo} onClick={toggleTodo} />
))}
</div>
);
这就是 React Redux 的 connect
所做的工作——它封装了与 Redux store 对话的逻辑,让你不用关心它。这就是你应该在你的实现中充分利用的东西。
mapDispatchToProps
的两种形式
mapDispatchToProps
参数可以有两种形式。虽然函数形式允许更多的自定义,但对象形式易于使用。
- 函数形式:允许更多自定义,获得对
dispatch
和可选ownProps
的访问权限 - 对象简写形式:更具声明性且更易于使用
⭐ 注意: 我们建议使用
mapDispatchToProps
的对象形式,除非你特别需要以某种方式自定义 dispatch 行为。
将 mapDispatchToProps
定义为函数
将 mapDispatchToProps
定义为函数可以让你在自定义组件接收的函数以及它们如何 dispatch actions 方面具有最大的灵活性。
你可以访问 dispatch
和 ownProps
。
你可以利用这个机会编写自定义函数以供你连接的组件调用。
参数
dispatch
ownProps
(可选)
dispatch
mapDispatchToProps
函数将使用 dispatch
作为第一个参数被调用。
你通常会通过返回在自身内部调用 dispatch()
的新函数来使用它,并直接传入一个普通的 action 对象或传入 action creator 的结果。
const mapDispatchToProps = (dispatch) => {
return {
// dispatch 普通的 actions
increment: () => dispatch({ type: 'INCREMENT' }),
decrement: () => dispatch({ type: 'DECREMENT' }),
reset: () => dispatch({ type: 'RESET' }),
};
};
你可能还希望将参数转发给你的 action creators:
const mapDispatchToProps = (dispatch) => {
return {
// 显式转发参数
onClick: (event) => dispatch(trackClick(event)),
// 隐式转发参数
onReceiveImpressions: (...impressions) =>
dispatch(trackImpressions(impressions)),
};
};
ownProps
(可选的)
如果你的 mapDispatchToProps
函数被声明为带两个参数,它将以 dispatch
作为第一个参数调用,并将 props
作为第二个参数传递给连接的组件,并且每当连接的组件接收到新的 props 时都会重新调用。
这意味着,不需要在组件重新渲染时将新的 props
重新绑定到 action dispatchers,而是可以在组件的 props
变更时这样做。
在组件 mount 时绑定
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 action 的函数。
- 如果你在
dispatch
中使用 action creators(与普通对象 actions 相反),则约定将字段键命名为与 action creator 相同的名称:
const increment = () => ({ type: 'INCREMENT' });
const decrement = () => ({ type: 'DECREMENT' });
const reset = () => ({ type: 'RESET' });
const mapDispatchToProps = (dispatch) => {
return {
// action creators 返回的 dispatching actions
increment: () => dispatch(increment()),
decrement: () => dispatch(decrement()),
reset: () => dispatch(reset()),
};
};
mapDispatchToProps
函数的返回值将作为 props 合并到你连接的组件中。你可以直接调用它们来 dispatch 其 action。
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>
);
}
(Counter 示例的完整代码是 in this CodeSandbox)
使用 bindActionCreators
定义 mapDispatchToProps
函数
手动包装这些函数很乏味,因此 Redux 提供了一个函数来简化它。
bindActionCreators
将值为 action creators 的对象转换为具有相同键的对象,但每个 action creator 都包裹在dispatch
中,因此可以直接调用它们。请参阅 关于bindActionCreators
的 Redux 文档
bindActionCreators
接收两个参数:
function
(an action creator) 或者object
(每个字段都是一个 action creator)dispatch
bindActionCreators
生成的包装函数将自动转发它们的所有参数,因此你无需手动执行此操作。
import { bindActionCreators } from 'redux';
const increment = () => ({ type: 'INCREMENT' });
const decrement = () => ({ type: 'DECREMENT' });
const reset = () => ({ type: 'RESET' });
// 绑定一个 action creator
// 返回 (...args) => dispatch(increment(...args))
const boundIncrement = bindActionCreators(increment, dispatch);
// 绑定一个全是 action creators 的对象
const boundActionCreators = bindActionCreators(
{ increment, decrement, reset },
dispatch
);
// 返回
// {
// 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);
}
// 组件接收 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 组件中 dispatch Redux actions 的设置遵循一个非常相似的过程:定义一个 action creator,将其包装在另一个看起来像 (…args) => dispatch(actionCreator(…args))
的函数中,并将该包装函数作为 props 传递给你的组件。
因为这很常见,connect
支持 mapDispatchToProps
参数的“对象简写”形式:如果你传递一个充满 action creators 的对象而不是一个函数,connect
将在内部自动为你调用 bindActionCreators
。
我们建议始终使用 mapDispatchToProps
的“对象简写”形式,除非你有特定原因需要自定义 dispatch 行为。
注意:
mapDispatchToProps
对象的每个字段都假定为一个 action creator- 你的组件将不再接收
dispatch
作为 prop
// React Redux 自动替你进行了该操作:
(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)
// 或者
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
。
换句话说,如果你:
// 组件接收 `dispatch`
connect(mapStateToProps /** 没有第二个参数 */)(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),
};
}
请参阅 this error in action in Redux’s GitHub issue #255。
这有一个关于当你指定 mapDispatchToProps
时是否为你的组件提供 dispatch
的讨论 (Dan Abramov’s response to #255 )。你可以阅读它们以进一步了解当前的实施意图。
我可以在 Redux 中不带 mapStateToProps
进行 mapDispatchToProps
吗?
是的。你可以通过传递 undefined
或 null
来跳过第一个参数。你的组件不会订阅 store,仍然会收到 mapDispatchToProps
定义的 dispatch props。
connect(null, mapDispatchToProps)(MyComponent);
我能调用 store.dispatch
吗?
这是在 React 组件中直接与 store 交互是一种反模式,无论是显式导入 store 还是通过上下文访问它(参见 Redux FAQ entry on store setup 了解更多详情)。让 React Redux 的 connect
处理对 store 的访问,并使用它传递给 props 的 dispatch
来 dispatch actions。
链接和参考
教程
相关文档
Q&A