Skip to main content

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 方面具有最大的灵活性。 你可以访问 dispatchownProps。 你可以利用这个机会编写自定义函数以供你连接的组件调用。

参数

  1. dispatch
  2. 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 接收两个参数:

  1. function (an action creator) 或者 object (每个字段都是一个 action creator)
  2. 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 吗?

是的。你可以通过传递 undefinednull 来跳过第一个参数。你的组件不会订阅 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