redux中的connect用法介紹及原理解析

react-redux 提供了兩個重要的對象, Providerconnect ,前者使 React 組件可被鏈接(connectable),後者把 React 組件和 Redux 的 store 真正鏈接起來。react-redux 的文檔中,對 connect 的描述是一段晦澀難懂的英文,在初學 redux 的時候,我對着這段文檔閱讀了好久,都沒有所有弄明白其中的意思(大概就是,單詞我都認識,連起來啥意思就不明白了的感受吧)。node

在使用了一段時間 redux 後,本文嘗試再次回到這裏,給 這段文檔 (同時摘抄在附錄中)一個靠譜的解讀。react


關於react-redux的一個流程圖git

流程圖github

connect用法介紹redux

connect方法聲明:後端

connect([mapStateToProps], [mapDispatchToProps], [mergeProps],[options])複製代碼

做用:鏈接React組件與 Redux store。api

參數說明:bash

mapStateToProps(state, ownProps) : stateProps複製代碼

這個函數容許咱們將 store 中的數據做爲 props 綁定到組件上。閉包

const mapStateToProps = (state) => {
  return {
    count: state.count
  }
}複製代碼

(1)這個函數的第一個參數就是 Redux 的 store,咱們從中摘取了 count 屬性。你沒必要將 state 中的數據原封不動地傳入組件,能夠根據 state 中的數據,動態地輸出組件須要的(最小)屬性。app

(2)函數的第二個參數 ownProps,是組件本身的 props。有的時候,ownProps 也會對其產生影響。

當 state 變化,或者 ownProps 變化的時候,mapStateToProps 都會被調用,計算出一個新的 stateProps,(在與 ownProps merge 後)更新給組件。

mapDispatchToProps(dispatch, ownProps): dispatchProps複製代碼

connect 的第二個參數是 mapDispatchToProps,它的功能是,將 action 做爲 props 綁定到組件上,也會成爲 MyComp 的 props。

[mergeProps],[options]複製代碼

無論是 stateProps 仍是 dispatchProps,都須要和 ownProps merge 以後纔會被賦給組件。connect 的第三個參數就是用來作這件事。一般狀況下,你能夠不傳這個參數,connect 就會使用 Object.assign 替代該方法。

[options] (Object) 若是指定這個參數,能夠定製 connector 的行爲。通常不用。

原理解析

首先connect之因此會成功,是由於Provider組件:

  • 在原應用組件上包裹一層,使原來整個應用成爲Provider的子組件
  • 接收Redux的store做爲props,經過context對象傳遞給子孫組件上的connect

那connect作了些什麼呢?

它真正鏈接 Redux 和 React,它包在咱們的容器組件的外一層,它接收上面 Provider 提供的 store 裏面的 state 和 dispatch,傳給一個構造函數,返回一個對象,以屬性形式傳給咱們的容器組件。

關於它的源碼

connect是一個高階函數,首先傳入mapStateToProps、mapDispatchToProps,而後返回一個生產Component的函數(wrapWithConnect),而後再將真正的Component做爲參數傳入wrapWithConnect,這樣就生產出一個通過包裹的Connect組件,該組件具備以下特色:

  • 經過props.store獲取祖先Component的store
  • props包括stateProps、dispatchProps、parentProps,合併在一塊兒獲得nextState,做爲props傳給真正的Component
  • componentDidMount時,添加事件this.store.subscribe(this.handleChange),實現頁面交互
  • shouldComponentUpdate時判斷是否有避免進行渲染,提高頁面性能,並獲得nextState
  • componentWillUnmount時移除註冊的事件this.handleChange

因爲connect的源碼過長,咱們只看主要邏輯:

export default function connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {}) {
  return function wrapWithConnect(WrappedComponent) {
    class Connect extends Component {
      constructor(props, context) {
        // 從祖先Component處得到store
        this.store = props.store || context.store
        this.stateProps = computeStateProps(this.store, props)
        this.dispatchProps = computeDispatchProps(this.store, props)
        this.state = { storeState: null }
        // 對stateProps、dispatchProps、parentProps進行合併
        this.updateState()
      }
      shouldComponentUpdate(nextProps, nextState) {
        // 進行判斷,當數據發生改變時,Component從新渲染
        if (propsChanged || mapStateProducedChange || dispatchPropsChanged) {
          this.updateState(nextProps)
            return true
          }
        }
        componentDidMount() {
          // 改變Component的state
          this.store.subscribe(() = {
            this.setState({
              storeState: this.store.getState()
            })
          })
        }
        render() {
          // 生成包裹組件Connect
          return (
            <WrappedComponent {...this.nextState} />
          )
        }
      }
      Connect.contextTypes = {
        store: storeShape
      }
      return Connect;
    }
  }複製代碼

connect使用實例

這裏咱們寫一個關於計數器使用的實例:

Component/Counter.js

import React, {Component} from 'react'

class Counter extends Component {
    render() {
        //從組件的props屬性中導入四個方法和一個變量
        const {increment, decrement, counter} = this.props;
        //渲染組件,包括一個數字,四個按鈕
        return (
            <p>
                Clicked: {counter} times
                {' '}
                <button onClick={increment}>+</button>
                {' '}
                <button onClick={decrement}>-</button>
                {' '}
            </p>
        )
    }
}

export default Counter;複製代碼

Container/App.js

import { connect } from 'react-redux'
import Counter from '../components/Counter'
import actions from '../actions/counter';

//將state.counter綁定到props的counter
const mapStateToProps = (state) => {
    return {
        counter: state.counter
    }
};
//將action的全部方法綁定到props上
const mapDispatchToProps = (dispatch, ownProps) => {
    return {
        increment: (...args) => dispatch(actions.increment(...args)),
        decrement: (...args) => dispatch(actions.decrement(...args))
    }
};

//經過react-redux提供的connect方法將咱們須要的state中的數據和actions中的方法綁定到props上
export default connect(mapStateToProps, mapDispatchToProps)(Counter)複製代碼



首先回顧一下 redux 的基本用法:

connect方法聲明以下:
connect([mapStateToProps], [mapDispatchToProps], [mergeProps],[options])  

做用:鏈接 React 組件與 Redux store。 
鏈接操做不會改變原來的組件類,反而返回一個新的已與 Redux store 鏈接的組件類。 

返回值
根據配置信息,返回一個注入了 state 和 action creator 的 React 組件。
複製代碼

[mapStateToProps(state, [ownProps]): stateProps] (Function): 若是定義該參數,組件將會監聽 Redux store 的變化。任什麼時候候,只要 Redux store 發生改變,mapStateToProps 函數就會被調用。該回調函數必須返回一個純對象,這個對象會與組件的 props 合併。若是你省略了這個參數,你的組件將不會監聽 Redux store。若是指定了該回調函數中的第二個參數 ownProps,則該參數的值爲傳遞到組件的 props,並且只要組件接收到新的 props,mapStateToProps 也會被調用。

[mapDispatchToProps(dispatch, [ownProps]): dispatchProps] (Object or Function): 若是傳遞的是一個對象,那麼每一個定義在該對象的函數都將被看成 Redux action creator,並且這個對象會與 Redux store 綁定在一塊兒,其中所定義的方法名將做爲屬性名,合併到組件的 props 中。若是傳遞的是一個函數,該函數將接收一個 dispatch 函數,而後由你來決定如何返回一個對象,這個對象經過 dispatch 函數與 action creator 以某種方式綁定在一塊兒(提示:你也許會用到 Redux 的輔助函數 bindActionCreators())。若是你省略這個 mapDispatchToProps 參數,默認狀況下,dispatch 會注入到你的組件 props 中。若是指定了該回調函數中第二個參數 ownProps,該參數的值爲傳遞到組件的 props,並且只要組件接收到新 props,mapDispatchToProps 也會被調用。

[mergeProps(stateProps, dispatchProps, ownProps): props] (Function): 若是指定了這個參數,mapStateToProps() 與 mapDispatchToProps() 的執行結果和組件自身的 props 將傳入到這個回調函數中。該回調函數返回的對象將做爲 props 傳遞到被包裝的組件中。你也許能夠用這個回調函數,根據組件的 props 來篩選部分的 state 數據,或者把 props 中的某個特定變量與 action creator 綁定在一塊兒。若是你省略這個參數,默認狀況下返回 Object.assign({}, ownProps, stateProps, dispatchProps) 的結果。

[options] (Object) 若是指定這個參數,能夠定製 connector 的行爲。

[pure = true] (Boolean): 若是爲 true,connector 將執行 shouldComponentUpdate 而且淺對比 mergeProps 的結果,避免沒必要要的更新,前提是當前組件是一個「純」組件,它不依賴於任何的輸入或 state 而只依賴於 props 和 Redux store 的 state。默認值爲 true。[withRef = false] (Boolean): 若是爲 true,connector 會保存一個對被包裝組件實例的引用,該引用經過 getWrappedInstance() 方法得到。默認值爲 false


靜態屬性

WrappedComponent (Component): 傳遞到 connect() 函數的原始組件類。

靜態方法

組件原來的靜態方法都被提高到被包裝的 React 組件。

實例方法

getWrappedInstance(): ReactComponent

僅當 connect() 函數的第四個參數 options 設置了 { withRef: true } 才返回被包裝的組件實例。

備註:

一、 函數將被調用兩次。第一次是設置參數,第二次是組件與 Redux store 鏈接:connect(mapStateToProps, mapDispatchToProps, mergeProps)(MyComponent)。

二、 connect 函數不會修改傳入的 React 組件,返回的是一個新的已與 Redux store 鏈接的組件,並且你應該使用這個新組件。

三、 mapStateToProps 函數接收整個 Redux store 的 state 做爲 props,而後返回一個傳入到組件 props 的對象。該函數被稱之爲 selector。


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 ,每當咱們在 storedispatch 一個 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 提供的 Providerconnect 方法。

使用 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 方法是重中之重。

connect 詳解

究竟 connect 方法到底作了什麼,咱們來一探究竟。

首先看下函數的簽名:

connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])

connect() 接收四個參數,它們分別是 mapStateToPropsmapDispatchToPropsmergePropsoptions

mapStateToProps(state, ownProps) : stateProps

這個函數容許咱們將 store 中的數據做爲 props 綁定到組件上。

const mapStateToProps = (state) => {
  return {
    count: state.count
  }
}
複製代碼

這個函數的第一個參數就是 Redux 的 store ,咱們從中摘取了 count 屬性。由於返回了具備 count 屬性的對象,因此 MyComp 會有名爲 countprops 字段。

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 中的數據鏈接到組件的基本方式。

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 屬性的對象,這兩個屬性也會成爲 MyCompprops

如上所示,調用 actions.increase() 只能獲得一個 action 對象 {type:'INCREASE'} ,要觸發這個 action 必須在 store 上調用 dispatch 方法。 diapatch 正是 mapDispatchToProps 的第一個參數。可是,爲了避免讓 MyComp 組件感知到 dispatch 的存在,咱們須要將 increasedecrease 兩個函數包裝一下,使之成爲直接可被調用的函數(即,調用該方法就會觸發 dispatch)。

Redux 自己提供了 bindActionCreators 函數,來將 action 包裝成直接可被調用的函數。

import {bindActionCreators} from 'redux';

const mapDispatchToProps = (dispatch, ownProps) => {
  return bindActionCreators({
    increase: action.increase,
    decrease: action.decrease
  });
}
複製代碼

一樣,當 ownProps 變化的時候,該函數也會被調用,生成一個新的 dispatchProps ,(在與 statePropeownProps merge 後)更新給 MyComp 。注意, action 的變化不會引發上述過程,默認 action 在組件的生命週期中是固定的。

[mergeProps(stateProps, dispatchProps, ownProps): props]

以前說過,無論是 stateProps 仍是 dispatchProps ,都須要和 ownProps merge 以後纔會被賦給 MyCompconnect 的第三個參數就是用來作這件事。一般狀況下,你能夠不傳這個參數, connect 就會使用 Object.assign 替代該方法。

其餘

最後還有一個 options 選項,比較簡單,基本上也不大會用到(尤爲是你遵循了其餘的一些 React 的「最佳實踐」的時候),本文就略過了。但願瞭解的同窗能夠直接看文檔。

1. 前言

隨着WEB應用變得愈來愈複雜,再加上node先後端分離愈來愈流行,那麼對數據流動的控制就顯得愈加重要。redux是在flux的基礎上產生的,基本思想是保證數據的單向流動,同時便於控制、使用、測試。

redux不依賴於任意框架(庫),只要subscribe相應框架(庫)的內部方法,就可使用該應用框架保證數據流動的一致性。

那麼如何使用redux呢?下面一步步進行解析,並帶有源碼說明,不只作到 知其然 ,還要作到 知其因此然 。

2. 主幹邏輯介紹(createStore)

2.1 簡單demo入門

先來一個直觀的認識:

// 首先定義一個改變數據的plain函數,成爲reducer
function count (state, action) {
  var defaultState = {
    year: 2015,
  };
  state = state || defaultState;
  switch (action.type) {
    case 'add':
      return {
        year: state.year + 1
      };
    case 'sub':
      return {
        year: state.year - 1
      }
    default :
      return state;
    }
  }

// store的建立
var createStore = require('redux').createStore;
var store = createStore(count);

// store裏面的數據發生改變時,觸發的回調函數
store.subscribe(function () {
  console.log('the year is: ', store.getState().year);
});

// action: 觸發state改變的惟一方法(按照redux的設計思路)
var action1 = { type: 'add' };
var action2 = { type: 'add' };
var action3 = { type: 'sub' };

// 改變store裏面的方法
store.dispatch(action1); // 'the year is: 2016 store.dispatch(action2); // 'the year is: 2017
store.dispatch(action3); // 'the year is: 2016複製代碼

2.2 挖掘createStore實現

爲了說明主要問題,僅列出其中的關鍵代碼,所有代碼,能夠點擊 這裏 閱讀。

a首先看createStore到底都返回的內容:

export default function createStore(reducer, initialState) {
  ...
  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer
  }
}複製代碼

每一個屬性的含義是: - dispatch: 用於action的分發,改變store裏面的state - subscribe: 註冊listener,store裏面state發生改變後,執行該listener - getState: 讀取store裏面的state - replaceReducer: 替換reducer,改變state修改的邏輯

b關鍵代碼解析

export default function createStore(reducer, initialState) {
  // 這些都是閉包變量
  var currentReducer = reducer
  var currentState = initialState
  var listeners = []
  var isDispatching = false;

  // 返回當前的state
  function getState() {
    return currentState
  }

  // 註冊listener,同時返回一個取消事件註冊的方法
  function subscribe(listener) {
    listeners.push(listener)
    var isSubscribed = true

    return function unsubscribe() {
    if (!isSubscribed) {
	   return
    }

    isSubscribed = false
    var index = listeners.indexOf(listener)
      listeners.splice(index, 1)
    }
  }
  // 經過action該改變state,而後執行subscribe註冊的方法
  function dispatch(action) {
    try {
      isDispatching = true
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }
    listeners.slice().forEach(listener => listener())
    return action
  }
  
  // 替換reducer,修改state變化的邏輯
  function replaceReducer(nextReducer) {
    currentReducer = nextReducer
    dispatch({ type: ActionTypes.INIT })
  }
  // 初始化時,執行內部一個dispatch,獲得初始state
  dispatch({ type: ActionTypes.INIT })
}複製代碼

若是還按照2.1的方式進行開發,那跟flux沒有什麼大的區別,須要手動解決不少問題,那redux如何將整個流程模板化(Boilerplate)呢?

3. 保證store的惟一性

隨着應用愈來愈大,一方面,不能把全部的數據都放到一個reducer裏面,另外一方面,爲每一個reducer建立一個store,後續store的維護就顯得比較麻煩。如何將兩者統一塊兒來呢?

3.1 demo入手

經過combineReducers將多個reducer合併成一個rootReducer: // 建立兩個reducer: count year function count (state, action) { state = state || {count: 1} switch (action.type) { default: return state; } } function year (state, action) { state = state || {year: 2015} switch (action.type) { default: return state; } }

// 將多個reducer合併成一個
var combineReducers = require('./').combineReducers;
var rootReducer = combineReducers({
  count: count,
  year: year,
});

// 建立store,跟2.1沒有任何區別
var createStore = require('./').createStore;
var store = createStore(rootReducer);

var util = require('util');
console.log(util.inspect(store));
//輸出的結果,跟2.1的store在結構上不存在區別
// { dispatch: [Function: dispatch],
//   subscribe: [Function: subscribe],
//   getState: [Function: getState],
//   replaceReducer: [Function: replaceReducer]
// }複製代碼

3.2 源碼解析combineReducers

// 高階函數,最後返回一個reducer
export default function combineReducers(reducers) {
  // 提出不合法的reducers, finalReducers就是一個閉包變量
  var finalReducers = pick(reducers, (val) => typeof val === 'function')
  // 將各個reducer的初始state均設置爲undefined
  var defaultState = mapValues(finalReducers, () => undefined)

  // 一個總reducer,內部包含子reducer
  return function combination(state = defaultState, action) {
    var finalState = mapValues(finalReducers, (reducer, key) => {
      var previousStateForKey = state[key]
      var nextStateForKey = reducer(previousStateForKey, action)
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
      return nextStateForKey
    );
    return hasChanged ? finalState : state
  }
  
}複製代碼

4. 自動實現dispatch

4.1 demo介紹

在2.1中,要執行state的改變,須要手動dispatch:

var action = { type: '***', payload: '***'};
dispatch(action);複製代碼

手動dispatch就顯得囉嗦了,那麼如何自動完成呢?

var bindActionCreators = require('redux').bindActionCreators;
// 能夠在具體的應用框架隱式進行該過程(例如react-redux的connect組件中)
bindActionCreators(action)複製代碼

4.2 源碼解析

// 隱式實現dispatch
function bindActionCreator(actionCreator, dispatch) {
  return (...args) => dispatch(actionCreator(...args))
}

export default function bindActionCreators(actionCreators, dispatch) {
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch)
  }
  return mapValues(actionCreators, actionCreator =>
    bindAQctionCreator(actionCreator, dispatch)
  )
}複製代碼

5. 支持插件 - 對dispatch的改造

5.1 插件使用demo

一個action能夠是同步的,也多是異步的,這是兩種不一樣的狀況, dispatch執行的時機是不同的:

// 同步的action creator, store能夠默認實現dispatch
function add() {
  return { tyle: 'add' }
}
dispatch(add());

// 異步的action creator,由於異步完成的時間不肯定,只能手工dispatch
function fetchDataAsync() {
  return function (dispatch) {
    requst(url).end(function (err, res) {
      if (err) return dispatch({ type: 'SET_ERR', payload: err});
      if (res.status === 'success') {
        dispatch({ type: 'FETCH_SUCCESS', payload: res.data });
      }
    })
  }
}複製代碼

下面的問題就變成了,如何根據實際狀況實現不一樣的dispatch方法,也便是根據須要實現不一樣的moddleware:

// 普通的dispatch建立方法
var store = createStore(reducer, initialState);
console.log(store.dispatch);

// 定製化的dispatch
var applyMiddleware = require('redux').applyMiddleware;
// 實現action異步的middleware
var thunk = requre('redux-thunk');
var store = applyMiddleware([thunk])(createStore);
// 通過處理的dispatch方法
console.log(store.dispatch);複製代碼

5.2 源碼解析

// next: 其實就是createStore
export default function applyMiddleware(...middlewares) {
  return (next) => (reducer, initialState) => {
    var store = next(reducer, initialState)
    var dispatch = store.dispatch
    var chain = []

    var middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    }
    chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch // 實現新的dispatch方法
    }
  }
}
// 再看看redux-thunk的實現, next就是store裏面的上一個dispatch
function thunkMiddleware({ dispatch, getState }) {
  return function(next) {
    return function(action) {
      typeof action === 'function' ? action(dispatch, getState) : next(action);
    }
  }
  
  return next => action =>
    typeof action === 'function' ?
      action(dispatch, getState) :
      next(action);
 }複製代碼

6. 與react框架的結合

6.1 基本使用

目前已經有現成的工具 react-redux 來實現兩者的結合:

var rootReducers = combineReducers(reducers);
var store = createStore(rootReducers);
var Provider = require('react-redux').Provider;
// App 爲上層的Component
class App extend React.Component{
  render() {
    return (
      <Provier store={store}>
        <Container />
      </Provider>
    );
  }
}

// Container做用: 1. 獲取store中的數據; 2.將dispatch與actionCreator結合起來
var connect = require('react-redux').connect;
var actionCreators = require('...');
// MyComponent是與redux無關的組件
var MyComponent = require('...');

function select(state) {
  return {
    count: state.count
  }
}
export default connect(select, actionCreators)(MyComponent)複製代碼

6.2 Provider – 提供store

React經過Context屬性,能夠將屬性(props)直接給子孫component,無須經過props層層傳遞, Provider僅僅起到得到store,而後將其傳遞給子孫元素而已:

export default class Provider extends Component {
  getChildContext() { // getChildContext: 將store傳遞給子孫component
    return { store: this.store }
  }

  constructor(props, context) {
    super(props, context)
    this.store = props.store
  }

  componentWillReceiveProps(nextProps) {
    const { store } = this
    const { store: nextStore } = nextProps

    if (store !== nextStore) {
      warnAboutReceivingStore()
    }
  }

  render() {
    let { children } = this.props
    return Children.only(children)
  }
}複製代碼

6.3 connect – 得到store及dispatch(actionCreator)

connect是一個高階函數,首先傳入mapStateToProps、mapDispatchToProps,而後返回一個生產 Component 的函數(wrapWithConnect),而後再將真正的Component做爲參數傳入wrapWithConnect(MyComponent),這樣就生產出一個通過包裹的Connect組件,該組件具備以下特色:

  • 經過this.context獲取祖先Component的store
  • props包括stateProps、dispatchProps、parentProps,合併在一塊兒獲得 nextState ,做爲props傳給真正的Component
  • componentDidMount時,添加事件this.store.subscribe(this.handleChange),實現頁面交互
  • shouldComponentUpdate時判斷是否有避免進行渲染,提高頁面性能,並獲得nextState
  • componentWillUnmount時移除註冊的事件this.handleChange
  • 在非生產環境下,帶有熱重載功能

主要的代碼邏輯:

export default function connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {}) {
  return function wrapWithConnect(WrappedComponent) {
    class Connect extends Component {
      constructor(props, context) {
        // 從祖先Component處得到store
        this.store = props.store || context.store
        this.stateProps = computeStateProps(this.store, props)
        this.dispatchProps = computeDispatchProps(this.store, props)
        this.state = { storeState: null }
        // 對stateProps、dispatchProps、parentProps進行合併
        this.updateState()
      }
      shouldComponentUpdate(nextProps, nextState) {
        // 進行判斷,當數據發生改變時,Component從新渲染
        if (propsChanged || mapStateProducedChange || dispatchPropsChanged) {
          this.updateState(nextProps)
            return true
          }
        }
        componentDidMount() {
          // 改變Component的state
          this.store.subscribe(() = {
            this.setState({
              storeState: this.store.getState()
            })
          })
        }
        render() {
          // 生成包裹組件Connect
          return (
            <WrappedComponent {...this.nextState} />
          )
        }
      }
      Connect.contextTypes = {
        store: storeShape
      }
      return Connect;
    }
  }複製代碼

7. redux與react-redux關係圖

相關文章
相關標籤/搜索