react-redux 之 connect 方法詳解
Redux 是「React 全家桶」中極為重要的一員,它試圖為 React 應用提供「可預測化的狀態管理」機制。Redux 本身足夠簡單,除了 React,它還能夠支援其他介面框架。所以如果要將 Redux 和 React 結合起來使用,就還需要一些額外的工具,其中最重要的莫過於 react-redux 了。
react-redux 提供了兩個重要的物件,Provider
和 connect
,前者使 React 元件可被連線(connectable),後者把 React 元件和 Redux 的 store 真正連線起來。react-redux 的文件中,對 connect
在使用了一段時間 redux 後,本文嘗試再次回到這裡,給這段文件(同時摘抄在附錄中)一個靠譜的解讀。
1. 預備知識
首先回顧一下 redux 的基本用法。如果你還沒有閱讀過 redux 的文件,你一定要先去閱讀一下。
const reducer = (state = {count: 0}, action) => {
switch (action.type){ case 'INCREASE': return {count: state.count + 1}; case 'DECREASE': return {count: state.count - 1}; default: return state; } } const actions = { increase: () => ({type: 'INCREASE'}), decrease: () => ({type: 'DECREASE'}) } const store = createStore(reducer); store.subscribe(() => console.log(store.getState()) ); store.dispatch(actions.increase()) // {count: 1} store.dispatch(actions.increase()) // {count: 2} store.dispatch(actions.increase()) // {count: 3}
通過 reducer
建立一個 store
,每當我們在 store
上 dispatch
一個 action
,store
內的資料就會相應地發生變化。
我們當然可以直接在 React 中使用 Redux:在最外層容器元件中初始化 store
,然後將 state
上的屬性作為 props
層層傳遞下去。
class App extends Component{ componentWillMount(){ store.subscribe((state)=>this.setState(state)) } render(){ return <Comp state={this.state} onIncrease={()=>store.dispatch(actions.increase())} onDecrease={()=>store.dispatch(actions.decrease())} /> } }
但這並不是最佳的方式。最佳的方式是使用 react-redux 提供的 Provider
和 connect
方法。
2. 使用 react-redux
首先在最外層容器中,把所有內容包裹在 Provider
元件中,將之前建立的 store
作為 prop
傳給 Provider
。
const App = () => {
return (
<Provider store={store}> <Comp/> </Provider> ) };
Provider
內的任何一個元件(比如這裡的 Comp
),如果需要使用 state
中的資料,就必須是「被 connect 過的」元件——使用 connect
方法對「你編寫的元件(MyComp
)」進行包裝後的產物。
class MyComp extends Component { // content... } const Comp = connect(...args)(MyComp);
可見,connect
方法是重中之重。
3. connect 詳解
究竟 connect
方法到底做了什麼,我們來一探究竟。
首先看下函式的簽名:
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
connect()
接收四個引數,它們分別是 mapStateToProps
,mapDispatchToProps
,mergeProps
和options
。
3.1. mapStateToProps(state, ownProps) : stateProps
這個函式允許我們將 store
中的資料作為 props
繫結到元件上。
const mapStateToProps = (state) => {
return {
count: state.count } }
這個函式的第一個引數就是 Redux 的 store
,我們從中摘取了 count
屬性。因為返回了具有 count
屬性的物件,所以 MyComp
會有名為 count
的 props
欄位。
class MyComp extends Component { render(){ return <div>計數:{this.props.count}次</div> } } const Comp = connect(...args)(MyComp);
當然,你不必將 state
中的資料原封不動地傳入元件,可以根據 state
中的資料,動態地輸出元件需要的(最小)屬性。
const mapStateToProps = (state) => {
return {
greaterThanFive: state.count > 5 } }
函式的第二個引數 ownProps
,是 MyComp
自己的 props
。有的時候,ownProps
也會對其產生影響。比如,當你在 store
中維護了一個使用者列表,而你的元件 MyComp
只關心一個使用者(通過 props
中的 userId
體現)。
const mapStateToProps = (state, ownProps) => {
// state 是 {userList: [{id: 0, name: '王二'}]}
return { user: _.find(state.userList, {id: ownProps.userId}) } } class MyComp extends Component { static PropTypes = { userId: PropTypes.string.isRequired, user: PropTypes.object }; render(){ return <div>使用者名稱:{this.props.user.name}</div> } } const Comp = connect(mapStateToProps)(MyComp);
當 state
變化,或者 ownProps
變化的時候,mapStateToProps
都會被呼叫,計算出一個新的 stateProps
,(在與 ownProps
merge 後)更新給 MyComp
。
這就是將 Redux store
中的資料連線到元件的基本方式。
3.2. mapDispatchToProps(dispatch, ownProps): dispatchProps
connect
的第二個引數是 mapDispatchToProps
,它的功能是,將 action 作為 props
繫結到 MyComp
上。
const mapDispatchToProps = (dispatch, ownProps) => {
return {
increase: (...args) => dispatch(actions.increase(...args)), decrease: (...args) => dispatch(actions.decrease(...args)) } } class MyComp extends Component { render(){ const {count, increase, decrease} = this.props; return (<div> <div>計數:{this.props.count}次</div> <button onClick={increase}>增加</button> <button onClick={decrease}>減少</button> </div>) } } const Comp = connect(mapStateToProps, mapDispatchToProps)(MyComp);
由於 mapDispatchToProps
方法返回了具有 increase
屬性和 decrease
屬性的物件,這兩個屬性也會成為 MyComp
的 props
。
如上所示,呼叫 actions.increase()
只能得到一個 action
物件 {type:'INCREASE'}
,要觸發這個 action
必須在 store
上呼叫 dispatch
方法。diapatch
正是 mapDispatchToProps
的第一個引數。但是,為了不讓 MyComp
元件感知到 dispatch
的存在,我們需要將 increase
和 decrease
兩個函式包裝一下,使之成為直接可被呼叫的函式(即,呼叫該方法就會觸發 dispatch
)。
Redux 本身提供了 bindActionCreators
函式,來將 action 包裝成直接可被呼叫的函式。
import {bindActionCreators} from 'redux';
const mapDispatchToProps = (dispatch, ownProps) => { return bindActionCreators({ increase: action.increase, decrease: action.decrease }); }
同樣,當 ownProps
變化的時候,該函式也會被呼叫,生成一個新的 dispatchProps
,(在與 statePrope
和 ownProps
merge 後)更新給 MyComp
。注意,action
的變化不會引起上述過程,預設 action
在元件的生命週期中是固定的。
3.3. [mergeProps(stateProps, dispatchProps, ownProps): props]
之前說過,不管是 stateProps
還是 dispatchProps
,都需要和 ownProps
merge 之後才會被賦給 MyComp
。connect
的第三個引數就是用來做這件事。通常情況下,你可以不傳這個引數,connect
就會使用 Object.assign
替代該方法。
3.4. 其他
最後還有一個 options
選項,比較簡單,基本上也不大會用到(尤其是你遵循了其他的一些 React 的「最佳實踐」的時候),本文就略過了。希望瞭解的同學可以直接看文件。
(完)
4. 附:connect 方法的官方英文文件
4.0.1. connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
Connects a React component to a Redux store.
It does not modify the component class passed to it. Instead, it returns a new, connected component class, for you to use.
4.0.2. Arguments
- [mapStateToProps(state, [ownProps]): stateProps] (Function): If specified, the component will subscribe to Redux store updates. Any time it updates, mapStateToProps will be called. Its result must be a plain object*, and it will be merged into the component’s props. If you omit it, the component will not be subscribed to the Redux store. If ownProps is specified as a second argument, its value will be the props passed to your component, and mapStateToProps will be additionally re-invoked whenever the component receives new props (e.g. if props received from a parent component have shallowly changed, and you use the ownProps argument, mapStateToProps is re-evaluated).
- [mapDispatchToProps(dispatch, [ownProps]): dispatchProps] (Object or Function): If an object is passed, each function inside it will be assumed to be a Redux action creator. An object with the same function names, but with every action creator wrapped into a dispatch call so they may be invoked directly, will be merged into the component’s props. If a function is passed, it will be given dispatch. It’s up to you to return an object that somehow uses dispatch to bind action creators in your own way. (Tip: you may use the bindActionCreators() helper from Redux.) If you omit it, the default implementation just injects dispatch into your component’s props. If ownProps is specified as a second argument, its value will be the props passed to your component, and mapDispatchToProps will be re-invoked whenever the component receives new props.
- [mergeProps(stateProps, dispatchProps, ownProps): props] (Function): If specified, it is passed the result of mapStateToProps(), mapDispatchToProps(), and the parent props. The plain object you return from it will be passed as props to the wrapped component. You may specify this function to select a slice of the state based on props, or to bind action creators to a particular variable from props. If you omit it, Object.assign({}, ownProps, stateProps, dispatchProps) is used by default.
- [options] (Object) If specified, further customizes the behavior of the connector.
- [pure = true] (Boolean): If true, implements shouldComponentUpdate and shallowly compares the result of mergeProps, preventing unnecessary updates, assuming that the component is a “pure” component and does not rely on any input or state other than its props and the selected Redux store’s state. Defaults to true.
- [withRef = false] (Boolean): If true, stores a ref to the wrapped component instance and makes it available via getWrappedInstance() method. Defaults to false.