React 全家桶之 react-redux

在談 react-redux 之前,我們先來回顧一下 react 組件的通信方式:

  1. 通過 props 父傳子,子傳孫。缺點是層級較深的話,傳遞會比較麻煩。消息橫向傳遞很麻煩。
  2. 消息訂閱發佈模式,需要自己實現監聽和觸發這一過程。消息可以同級橫向傳遞,如 A 和 B 都是 C 的子組件,可以在 C 組件中調度 A、B 的監聽函數。
  3. 通過 react 中的上下文對象 context 來傳遞消息,由於 context 全局獨一份,所以可以實現跨層級、橫向的消息傳遞

我們來看一下第三種方式,context 的使用姿勢

class Man extends React.Component {
  getChildrenContext () {
    return {
      num: 100
    }
  }
  render () {
    <div>
      <Son>
        <Grandson></Grandson>
      </Son>
    </div>
  }
}

Man.childContextTypes = {
  num: React.Prototypes.number
}
class Grandson extends React.Component {
  render () {
    return (
    <div>{ this.context.num }</div>
    )
  }
}
Grandson.contextTypes = {
  num: React.Prototypes.number
}

可以看到 Man 組件中的 num 是可以跨層級傳遞給孫子組件 Grandson 的,而 react-redux 正是利用了這一點,基於 context 實現了數據的共享。

話不多說,先上代碼,看一下 react-redux 怎麼用,還是以上一篇文章【前端架構系列—React 全家桶之 redux】中的代碼爲例。reducer 和 store 都不用變,只是 component 改一下就行了。

這也好理解,reducer 和 store 是純 redux 的部分,而 react-redux 是 react 和 redux 之間橋樑,所以要引入 react-redux,只要在原先 react 中實接使用 redux 的地方動動刀子就行了。話不多說先上代碼

src/App.js,將項目頂層組件封裝在 Provider 組件內,並將 store 對象做爲其屬性。這樣一來,所有的子組件都可以拿到 store 中的數據

import { Provider } from 'react-redux';
import store from './store';

class App extends React.Component {

  render() {
    return (
      <Provider store={ store }>
        <Counter />
      </Provider>
    );
  }

}

export default App;

components/counter.js,有如下幾點的改變

  1. 輸入:原先通過 store.getState() 拿到數據,現在變成了通過 mapStateToProps,將 store 中的數據掛載到組件的 props 上
  2. 輸出:原先通過 store.dispatch() 觸發 action,現在變成了通過 mapDispatchToProps,將 actionsCreator 中的方法掛載到組件的 props 上。且這些 actionCreator 經過 bindActionCreators 封裝之後具備了 dispatch 的功能。這樣一來,在組件中可以直接調用 this.props.action() 來觸發一個 action
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux'
import actionCreator from '../actions/counter'

class Counter extends React.Component {
  render() {
    const { num, actions } = this.props;
    return (
      <div className="App">
        {
          num
        }
        <div></div>
        <button onClick={ actions.increment }>+</button>
        <button onClick={ actions.decrement }>-</button>
      </div>
    );
  }

}

const mapStateToProps = (state, ownProps) => {
  return {
    num: state.counter.num
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    actions: bindActionCreators(actionCreator, dispatch)
  }
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Counter);

整個過程用到了幾個重要的 API

Provider:頂層組件,作用是把 store 掛載到 context 上,以便下層組件可以從 context 或者 props 中獲取 store

connect:高階組件,獲取到 context 上的 store 以及 actionCreator,然後封裝業務組件,以屬性的方式傳遞給業務組件

mapStateToProps:selector 選擇器,從 store 中的選出組件需要的值,做爲 selector 的返回值

mapDispatchToProps:selector選擇器,提取某些 actionCreator,做爲 selector 的返回值

下面分別大致說一下每個方法的實現過程

Provider 組件

Provider 組件的作用就是把 store 掛載到 context 上面,把項目的根組件包裹起來,這樣任意一個業務組件都可以通過 context 拿到 store 中的數據

class Provider extends React.Component {
    getChildContext () {
        return {
            store: this.props.store
        }
    }
    render () {
        return Children.only(this.props.children)
    }
}

Provider.propTypes = {
    store: PropTypes.shape({
      subscribe: PropTypes.func.isRequired,
      dispatch: PropTypes.func.isRequired,
      getState: PropTypes.func.isRequired
    }),
    context: PropTypes.object,
    children: PropTypes.any
}

connect 函數

connect 是一個高階組件,它接收 mapStateToProps、mapDispatchToProps、mergeProps、options 作爲參數。它的作用就是將  store 中的 state 和 dispatch 映射到組件的 props 上。這樣組件可以輕易的獲取 state 和 發出 action.

render() {
  const selector = this.selector
  selector.shouldComponentUpdate = false

  if (selector.error) {
    throw selector.error
  } else {
    return createElement(WrappedComponent, this.addExtraProps(selector.props))
  }
}

mapStateToProps

store 中的 state 是整個項目的數據,而單個業務組件只需要這顆 state 樹中的某幾個數據。所以該對象的作用就是摘取組件所需要的幾個 state。

可以看到如果我們沒有傳 mapStateToProps 這個參數給 connect 函數的話,connect 組件並不會監聽 state 的變化來更新組件。

// if mapStateToProps is falsy, the Connect component doesn't subscribe to store state changes
shouldHandleStateChanges: Boolean(mapStateToProps),

相反,如果傳了就會初始化一些監聽

if (!shouldHandleStateChanges) return

  const parentSub = (this.propsMode ? this.props : this.context)[subscriptionKey]
  this.subscription = new Subscription(this.store, parentSub, this.onStateChange.bind(this))

  this.notifyNestedSubs = this.subscription.notifyNestedSubs.bind(this.subscription)
}

mapDispatchToProps

改對象的傳值有三種情況:

  • function:
  • Object:這時會調用 redux 的 bindActionCreators,將這個 actionCreator 對象封裝起來,是這個 creator 被調用的時候,直接就能 dispatch
  • 不傳
export function whenMapDispatchToPropsIsFunction(mapDispatchToProps) {
  return (typeof mapDispatchToProps === 'function')
    ? wrapMapToPropsFunc(mapDispatchToProps, 'mapDispatchToProps')
    : undefined
}

export function whenMapDispatchToPropsIsMissing(mapDispatchToProps) {
  return (!mapDispatchToProps)
    ? wrapMapToPropsConstant(dispatch => ({ dispatch }))
    : undefined
}

export function whenMapDispatchToPropsIsObject(mapDispatchToProps) {
  return (mapDispatchToProps && typeof mapDispatchToProps === 'object')
    ? wrapMapToPropsConstant(dispatch => bindActionCreators(mapDispatchToProps, dispatch))
    : undefined
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章