1. 程式人生 > >react-redux 之 connect 方法詳解

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 的時候,我對著這段文件閱讀了很久,都沒有全部弄明白其中的意思(大概就是,單詞我都認識,連起來啥意思就不明白了的感覺吧)。

在使用了一段時間 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 一個 actionstore 內的資料就會相應地發生變化。

我們當然可以直接在 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() 接收四個引數,它們分別是 mapStateToPropsmapDispatchToPropsmergePropsoptions

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 之後才會被賦給 MyCompconnect 的第三個引數就是用來做這件事。通常情況下,你可以不傳這個引數,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.