跳到主要內容

連線:使用 `mapDispatchToProps` 來派送動作

傳入 `connect` 的第二個引數 `mapDispatchToProps` 用於將動作派送到 store 中。

`dispatch` 是 Redux store 的函數。你可以呼叫 `store.dispatch` 來派送動作。這是觸發狀態變更的唯一途徑。

有了 React Redux,你的元件永遠不會直接存取 store —— `connect` 會幫你處理。React Redux 提供了兩種方法讓元件派送動作

  • 預設情況下,連接的元件會接收 `props.dispatch`,並可以自行派送動作。
  • `connect` 可以接受一個名為 `mapDispatchToProps` 的引數,它允許你建立呼叫時會派送的函數,並將這些函數作為 `prop` 傳遞給你的元件。

`mapDispatchToProps` 函數通常簡稱為 `mapDispatch`,但實際使用的變數名稱可以隨意取用。

派送方法

預設:`dispatch` 作為 Prop

如果你未指定第一個參數給 connect(),你的組件將預設會收到 dispatch。舉例來說

connect()(MyComponent)
// which is equivalent with
connect(null, null)(MyComponent)

// or
connect(mapStateToProps /** no second argument */)(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 的形式提供動作發送函式。如此一來,你就可以直接呼叫 props.increment(),而非 props.dispatch(() => increment())。會想這麼做有幾個原因。

宣告性更強

首先,將發送邏輯封裝到函式中能使實作更具宣告性。發送一個動作,並讓 Redux store 處理資料流程,這是一種行為的「實作方式」,而非它「做了什麼」。

一個好的範例是在按鈕被按下的時候發送一個動作。將按鈕直接連接起來在概念上來說可能不太合適,而讓按鈕參照 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 store 通信的邏輯,讓你不用擔心它。而這正是你在實作中應該充分利用的。

兩種形式的 mapDispatchToProps

mapDispatchToProps 參數可以是兩種形式。函式形式允許更多的自訂,而物件形式則較易於使用。

  • 函式形式:允許更多自訂,能存取 dispatch,並有選擇地存取 ownProps
  • 物件速記形式:宣告性更強,且較易於使用

註:除非你特別需要以某種方式自訂發送行為,否則我們建議使用物件形式的 mapDispatchToProps

mapDispatchToProps 定義為函式

mapDispatchToProps 定義為函式讓您能靈活地自訂元件接收的函式,以及函式如何發出動作。您會存取到 dispatchownProps。您可藉此機會撰寫自訂函式,讓連接元件呼叫。

參數

  1. dispatch
  2. ownProps(選用)

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 函式的宣告使用兩個參數,它會以 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 內部使用動作產生器(與一般物件動作相反),慣例是將欄位金鑰命名為與動作產生器相同的名稱
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 函式的傳回值會合併到連接元件作為道具。您可以直接呼叫它們以發出動作。

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 接受兩個參數

  1. 一個 函式(動作建立器)或一個 物件(每個欄位是一個動作建立器)
  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)) 的函式中,並將該包裝函式作為一個 prop 傳遞給你的元件。

由於這很常見,因此 connectmapDispatchToProps 參數支援一個「物件簡寫」形式:如果你傳遞一個充滿動作建立器的物件而不是函式,connect 會在你內部自動呼叫 bindActionCreators

除非有特定的理由需要自訂傳送的行為,我們建議總是使用 mapDispatchToProps 的「物件簡寫」形式。

請注意

  • 假設 mapDispatchToProps 物件的各個欄位是一個動作建立器
  • 你的元件將不再接收 dispatch 做為一個 prop
// 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 議題 #255 中的這項動作中發生的錯誤

您可以在指定 `mapDispatchToProps` 時,針對是否要將 `dispatch` 提供給您的元件進行討論(Dan Abramov 對 #255 的回應)。您可以閱讀它們,以進一步瞭解目前的實作意圖。

我在 Redux 中沒有 `mapStateToProps` 也可以使用 `mapDispatchToProps` 嗎?

可以。您可以透過傳遞 `undefined` 或 `null` 來略過第一個參數。您的元件不會訂閱儲存庫,而且仍會收到 `mapDispatchToProps` 定義的 dispatch props。

connect(null, mapDispatchToProps)(MyComponent)

我可以呼叫 `store.dispatch` 嗎?

與儲存庫直接進行互動是 React 元件中的反模式,無論是明確匯入儲存庫或透過內容存取儲存庫(請參閱 有關設定儲存庫的 Redux 常見問題解答 以取得更多詳細資訊)。讓 React Redux 的 `connect` 處理對儲存庫的存取,並使用它傳遞給 props 的 `dispatch` 來執行 dispatch 動作。

教學

相關文件

問答