連線:使用 `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
定義為函式讓您能靈活地自訂元件接收的函式,以及函式如何發出動作。您會存取到 dispatch
和 ownProps
。您可藉此機會撰寫自訂函式,讓連接元件呼叫。
參數
dispatch
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
接受兩個參數
- 一個
函式
(動作建立器)或一個物件
(每個欄位是一個動作建立器) 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 傳遞給你的元件。
由於這很常見,因此 connect
為 mapDispatchToProps
參數支援一個「物件簡寫」形式:如果你傳遞一個充滿動作建立器的物件而不是函式,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 動作。
連結與參考
教學
相關文件
問答