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
.
Вы можете использовать эту возможность для написания ваших функций, которые будут вызываться вашими подключенными компонентами.
Аргументы
dispatch
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
, таким образом они могут быть вызваны напрямую. Посмотрите документацию ReduxbindActionCreators
bindActionCreators
принимает 2 параметра:
function
(создатель действия) илиobject
(каждое поле которого является создателем действия)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
, передаваемый в пропсы, для отправки действий.
Ссылки и источники
Руководства
Связанная документация
Вопросы и ответы
- Как получить обычный dispatch из
this.props
, используя connect с Redux? this.props.dispatch
равенundefined
при указанииmapDispatchToProps
- Не вызывайте
store.dispatch
, вместо него используйтеthis.props.dispatch
внедрённый функциейconnect
- Могу ли я указать
mapDispatchToProps
безmapStateToProps
в Redux? - Redux документация FAQ: React Redux