Skip to main content

Connect: Отправка действий с помощью mapDispatchToProps

В качестве второго аргумента, передаваемого в connect, mapDispatchToProps используется для отправки(dispatch) действий в хранилище(store).

dispatch — это функция Redux хранилища(store). Вы вызываете store.dispatch, чтобы отправить действие. Это единственный способ вызвать изменение состояния.

С React Redux ваши компоненты никогда не обращаются к хранилищу(store) напрямую — connect делает это за вас. React Redux дает вам два способа разрешить компонентам отправлять действия:

  • По умолчанию подключенный компонент получает props.dispatch и может сам отправлять действия.
  • connect может принимать аргумент с именем mapDispatchToProps, который позволяет вам создавать функции, отправляющие действие при вызове, и передавать их в качестве пропсов вашему компоненту.

Функции mapDispatchToProps обычно для краткости называются mapDispatch, но фактическое используемое имя переменной может быть любым.

Подходы к отправке(dispatch)

По умолчанию: dispatch как пропс

Если вы не укажете второй аргумент для connect(), ваш компонент по умолчанию получит dispatch. Пример:

connect()(MyComponent)
// эквивалентно
connect(null, null)(MyComponent)

// или
connect(mapStateToProps /** нет второго аргумента */)(MyComponent)

После того, как вы подключили свой компонент таким образом, ваш компонент получает props.dispatch. Вы можете использовать его для отправки действий в хранилище(store).

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.dispatch(() => increment()) вы можете напрямую вызвать props.increment(). Есть несколько причин, по которым вы можете захотеть это сделать.

Это более декларативный подход

Во-первых, инкапсуляция логики отправки(dispatch) в функцию делает реализацию более декларативной. Отправка действия и предоставление возможности Redux хранилищу(store) обрабатывать поток данных — отвечает на вопрос как реализовать поведение, нежели что оно делает.

Хорошим примером может быть отправка(dispatch) действия при нажатии кнопки. Вызов функции отправки напрямую в кнопке не имеет смысла концептуально, поскольку в таком случае кнопка должна знать о функции dispatch.

// кнопка должна знать об "dispatch"
<button onClick={() => dispatch({ type: "SOMETHING" })} />

// кнопка не знает об "dispatch",
<button onClick={doSomething} />

После того, как вы обернули все наши создатели действий функциями, которые отправляют действия, компонент свободен от необходимости вызывать функцию dispatch. Следовательно, если вы определите свой собственный mapDispatchToProps, подключенный компонент больше не будет получать dispatch.

Передача логики отправки(dispatch) действия в ( неподключенные ) дочерние компоненты

Кроме того, вы также получаете возможность передавать функции отправки действий дочерним (вероятно, неподключенным) компонентам. Это позволяет большему количеству компонентов отправлять действия, оставляя их «не осведомленными» о Redux.

// передача toggleTodo дочернему компоненту
// предоставление Todo возможности отправлять(dispatch) действие toggleTodo
const TodoList = ({ todos, toggleTodo }) => (
<div>
{todos.map((todo) => (
<Todo todo={todo} onClick={toggleTodo} />
))}
</div>
)

Это то, что делает connect React Redux — он инкапсулирует логику общения с Redux хранилищем(store) и позволяет вам не беспокоиться о ней. И это то, что вы точно должны использовать в своей реализации.

Две формы mapDispatchToProps

Параметр mapDispatchToProps может иметь две формы. В то время как функциональная форма допускает больше настроек, объектная форма более проста в использовании.

  • Функциональная форма: допускает больше настроек, получает доступ к dispatch и, при необходимости, к ownProps
  • Сокращенная объектная форма: более декларативная и простая в использовании.

Примечание. Мы рекомендуем использовать объектную форму mapDispatchToProps, если только вам не нужно каким-то образом настроить поведение отправки(dispatch).

Объявление mapDispatchToProps как функции

Объявление mapDispatchToProps как функции дает вам максимальную гибкость в настройке функций, которые получает ваш компонент, и того, как они отправляют действия. Вы получаете доступ к dispatch и ownProps. Вы можете использовать эту возможность для написания ваших функций, которые будут вызываться вашими подключенными компонентами.

Аргументы

  1. dispatch
  2. ownProps (необязателен)

dispatch

Функция mapDispatchToProps будет вызываться с dispatch в качестве первого аргумента. Обычно это используется для возврата новых функций, которые вызывают dispatch() внутри себя и передают им либо простой объект действия напрямую, либо результат создателя действия.

const mapDispatchToProps = (dispatch) => {
return {
// отправка (dispatch) простых действий
increment: () => dispatch({ type: 'INCREMENT' }),
decrement: () => dispatch({ type: 'DECREMENT' }),
reset: () => dispatch({ type: 'RESET' }),
}
}

Вы также, вероятно, захотите передать аргументы создателям действий:

const mapDispatchToProps = (dispatch) => {
return {
// явная пересылка аргументов
onClick: (event) => dispatch(trackClick(event)),

// неявная пересылка аргументов
onReceiveImpressions: (...impressions) =>
dispatch(trackImpressions(impressions)),
}
}

ownProps ( необязателен )

Если ваша функция mapDispatchToProps объявлена с двумя параметрами, она будет вызываться с dispatch в качестве первого параметра и 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 должна возвращать простой объект:

  • Каждое поле объекта станет независимым пропсом вашего компонента и его значение обычно является функцией, отпраляющей(dispatch) действия при вызове.
  • Если вы используете dispatch внутри создателей действий ( в качестве альтернативы действиям из объекта ), то по конвенции следует назвать поле объекта тем же именем что и создателя действий:
const increment = () => ({ type: 'INCREMENT' })
const decrement = () => ({ type: 'DECREMENT' })
const reset = () => ({ type: 'RESET' })

const mapDispatchToProps = (dispatch) => {
return {
// отправка(dispatch) действий возвращаемых из создателей действий
increment: () => dispatch(increment()),
decrement: () => dispatch(decrement()),
reset: () => dispatch(reset()),
}
}

Результат функции mapDispatchToProps будет объединён с пропсами подключенного компонента. Вы можете вызвать их, тем самым отправить(dispatch) их действие.

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 на CodeSandbox)

Определяем функцию mapDispatchToProps с bindActionCreators

Ручная обёртка этих функций утомительна, к счастью, Redux предоставляет функцию, упрощающую это.

bindActionCreators превращает объект, значениями которого выступают создатели действий, в объект с теми же ключами, при этом каждый создатель действия оборачивается в вызов dispatch, таким образом они могут быть вызваны напрямую. Посмотрите документацию Redux bindActionCreators

bindActionCreators принимает 2 параметра:

  1. function (создатель действия) или object (каждое поле которого является создателем действия)
  2. dispatch

Функции обёртки, сгенерированные bindActionCreators, будут автоматически пробрасывать их аргументы, поэтому у вас нет необходимости в ручной работе.

import { bindActionCreators } from 'redux'

const increment = () => ({ type: 'INCREMENT' })
const decrement = () => ({ type: 'DECREMENT' })
const reset = () => ({ type: 'RESET' })

// Привязка создателя действия
// Возвращает (...args) => dispatch(increment(...args))
const boundIncrement = bindActionCreators(increment, dispatch)

// Привязка объекта с создателями действий
const boundActionCreators = bindActionCreators(
{ increment, decrement, reset },
dispatch
)
// Возвращает
// {
// increment: (...args) => dispatch(increment(...args)),
// decrement: (...args) => dispatch(decrement(...args)),
// reset: (...args) => dispatch(reset(...args)),
// }

Использование bindActionCreators в нашей функции mapDispatchToProps:

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 как объект

Вы видели, что настройка отправки действий Redux в компоненте React выполняется очень похожим образом: определяем создатель действия, оборачиваем его в другую функцию, выглядящую как (…args) => dispatch(actionCreator(…args)), и передаём функцию обёртки как пропс вашему компоненту.

Поскольку это распространено, connect поддерживает форму "сокращённого объекта" (“object shorthand”) для аргумента mapDispatchToProps: если вы передадите объект, состоящий из создателей действий вместо функции, connect автоматически вызовет bindActionCreators за вас.

Мы рекомендуем всегда использовать форму "сокращённого объекта" (“object shorthand”) для mapDispatchToProps, кроме случаев, когда у вас есть конкретная причина для настройки поведения отправки (dispatch).

Обратите внимание на это:

  • Предполагается, что каждое поле объекта mapDispatchToProps, является создателем действия
  • Ваши компоненты больше не получат dispatch пропсом
// 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

Вы можете вернуть dispatch из вашей функции mapDispatchToProps:

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.

Существуют дискуссии по поводу передачи dispatch в пользовательские компоненты при указании mapDispatchToProps ( ответ Дэна Абрамова на #255 ). Вы можете прочитать их для дальнейшего понимания текущих намерений по реализации.

Могу ли я вызывать mapDispatchToProps без mapStateToProps в Redux?

Да. Вы можете пропустить первый параметр указанием undefined или null. Ваш компонент не будет подписываться на хранилище(store) и всё ещё будет получать пропсом dispatch, определённый в mapDispatchToProps.

connect(null, mapDispatchToProps)(MyComponent)

Могу ли я вызвать store.dispatch?

Взаимодействие с хранилищем(store) напрямую из React компонента является антипаттерном, будь то явный импорт магазина или доступ к нему через контекст (смотрите запись Redux FAQ по настройке хранилища (store) для дальнейших деталей). Позвольте connect обработать доступ в хранилище(store) и используйте dispatch, передаваемый в пропсы, для отправки действий.

Ссылки и источники

Руководства

Связанная документация

Вопросы и ответы