react-redux的Provider和connect

react-redux簡介html

redux是一個數據管理框架,而react-redux是專門針對react開發的一個插件。react-redux提供了2個API,Provider和connect。原本打算在一篇文章同時講解2個API的實現,不過看了一下connect的源碼,368行,仍是分開解析吧。node

本文帶領你們分析Provider的核心代碼。react

如何使用Providergit

咱們先了解在react項目中是如何使用Provider。github

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

    const store = configureStore();
    ReactDOM.render((
        <Provider store={store}>
            
        </Provider>),
        document.getElementById('root')
    );複製代碼

上面的代碼能夠看出,使用Provider分爲下面幾個步驟:redux

一、導入Provider 這裏跟小白分享一個小知識,你能夠看到Provider加了個大括號,而第二個import configureStore沒有加大括號,這是由於react-redux的文件中沒有指定default輸出。若是指定了export default,則不須要加大括號,注意一個js文件只能有一個default。promise

import { Provider } from 'react-redux';複製代碼

二、將store做爲參數傳入Provider。瀏覽器

<Provider store={store}>
        
    </Provider>複製代碼

Provider源碼bash

import { Component, Children } from 'react'
    import PropTypes from 'prop-types'
    import storeShape from '../utils/storeShape'
    import warning from '../utils/warning'
    
    let didWarnAboutReceivingStore = false
    function warnAboutReceivingStore() {
      if (didWarnAboutReceivingStore) {
        return
      }
      didWarnAboutReceivingStore = true
    
      warning(
        '<Provider> does not support changing `store` on the fly. ' +
        'It is most likely that you see this error because you updated to ' +
        'Redux 2.x and React Redux 2.x which no longer hot reload reducers ' +
        'automatically. See https://github.com/reactjs/react-redux/releases/' +
        'tag/v2.0.0 for the migration instructions.'
      )
    }
    
    export default class Provider extends Component {
      getChildContext() {
        return { store: this.store }
      }
    
      constructor(props, context) {
        super(props, context)
        this.store = props.store
      }
    
      render() {
        return Children.only(this.props.children)
      }
    }
    
    if (process.env.NODE_ENV !== 'production') {
      Provider.prototype.componentWillReceiveProps = function (nextProps) {
        const { store } = this
        const { store: nextStore } = nextProps
    
        if (store !== nextStore) {
          warnAboutReceivingStore()
        }
      }
    }
    
    Provider.propTypes = {
      store: storeShape.isRequired,
      children: PropTypes.element.isRequired
    }
    Provider.childContextTypes = {
      store: storeShape.isRequired
    }複製代碼

Provider源碼解析react-router

Provider只有一個參數,很是簡單,代碼也僅有55行。

一、Provider是一個react組件

import { Component, Children } from 'react'
    import PropTypes from 'prop-types'
    import storeShape from '../utils/storeShape'
    import warning from '../utils/warning'

    export default class Provider extends Component {
      getChildContext() {
        return { store: this.store }
      }
    
      constructor(props, context) {
        super(props, context)
        this.store = props.store
      }
    
      render() {
        return Children.only(this.props.children)
      }
    }複製代碼

Provider組件寫了3個方法,getChildContext、constructor、render。

constructor是構造方法,this.store = props.store中的this表示當前的組件。在構造函數定義this.store的做用是爲了可以在getChildContext方法中讀取到store。

你最不熟悉的可能就是getChildContext,翻譯過來就是上下文。什麼意思呢?又有什麼用呢?咱們看到getChildContext方法是返回store。接着,就看不到store哪去了。

最後執行render渲染,返回一個react子元素。Children.only是react提供的方法,this.props.children表示的是隻有一個root的元素。

二、給Provider組件設置propTypes驗證。storeShape是一個封裝的方法。

Provider.propTypes = {
        store: storeShape.isRequired,
        children: PropTypes.element.isRequired
    }

  
    //storeShape
    import PropTypes from 'prop-types'
    
    export default PropTypes.shape({
      subscribe: PropTypes.func.isRequired,
      dispatch: PropTypes.func.isRequired,
      getState: PropTypes.func.isRequired
    })複製代碼

三、驗證childContextTypes 它的做用就是讓Provider下面的子組件可以訪問到store。 詳細解釋和用法看 react關於context的介紹

Provider.childContextTypes = {
      store: storeShape.isRequired
    }複製代碼

四、node運行環境判斷 若是不是生產環境,也就是在開發環境中,實現componentWillReceiveProps()。

if (process.env.NODE_ENV !== 'production') {
      Provider.prototype.componentWillReceiveProps = function (nextProps) {
        const { store } = this
        const { store: nextStore } = nextProps
    
        if (store !== nextStore) {
          warnAboutReceivingStore()
        }
      }
    }複製代碼

其實也能夠把這段代碼寫到Provider組件內部去。

他的做用是當接收到新的props的時候,若是是在開發環境下,就判斷當前的store和下一個store是否是不相等,若是是,就執行warnAboutReceivingStore()。

export default class Provider extends Component {
      
      componentWillReceiveProps(nextProps) {
        if (process.env.NODE_ENV !== 'production') {
          const { store } = this
          const { store: nextStore } = nextProps
    
          if (store !== nextStore) {
            warnAboutReceivingStore()
          }
        }
      }
      
      render() {
        return Children.only(this.props.children)
      }
    }複製代碼

五、warnAboutReceivingStore的做用。 上面說到執行了warnAboutReceivingStore,那麼warnAboutReceivingStore的做用是什麼呢?

let didWarnAboutReceivingStore = false
        function warnAboutReceivingStore() {
          if (didWarnAboutReceivingStore) {
            return
          }
          didWarnAboutReceivingStore = true
          
          warning(
        '<Provider> does not support changing `store` on the fly. ' +
        'It is most likely that you see this error because you updated to ' +
        'Redux 2.x and React Redux 2.x which no longer hot reload reducers ' +
        'automatically. See https://github.com/reactjs/react-redux/releases/' +
        'tag/v2.0.0 for the migration instructions.'
      )複製代碼

didWarnAboutReceivingStore是一個開關的做用,默認是false,也就是不執行warning操做。當props更新的時候,執行了warnAboutReceivingStore(),若是didWarnAboutReceivingStore爲true,則return,不然就將didWarnAboutReceivingStore設置爲true。而後就會執行warning的警告機制。

這樣作的目的是不容許在componentWillReceiveProps作store的更新操做。

總結

很快就到尾聲了,Provider是一個react組件,提供了一個參數store,而後渲染了一個子組件,咱們一般把路由渲染成子組件,最後還處理了一個異常狀況,提供了warning提示。

大部分時候是這樣用的。在react-router4中,也支持這種寫法,Provider也能夠直接嵌套在自定義的react組件中。

<Provider store={store}>
          <Router history={hashHistory}>
                {routes}
          </Router>
    </Provider>複製代碼





在redux的配置文件中,若是你使用了redux-logger,也許你會寫下面這樣一段代碼:

import thunk from 'redux-thunk';
    import promise from 'redux-promise';
    import createLogger from 'redux-logger';
    
    const logger = createLogger();
    const createStoreWithMiddleware = applyMiddleware(thunk, promise, logger)(createStore);
    const store = createStoreWithMiddleware(reducer);複製代碼

如今,咱們只關注redux-logger,咱們能夠看到使用redux-logger分爲下面幾個步驟:

一、導入redux-logger

import createLogger from 'redux-logger';複製代碼

二、運行createLogger方法,將返回結果賦值給常量

const logger = createLogger();複製代碼

三、將looger傳入applyMiddleware()

applyMiddleware(logger)複製代碼

有2個難點,第一是createLogger()的返回值究竟是如何實現的。第二就是applyMiddleware方法如何處理返回值。由於本文是講redux-logger的實現,因此咱們只分析createLogger()

redux-logger中createLogger方法源碼

const repeat = (str, times) => (new Array(times + 1)).join(str);
    const pad = (num, maxLength) => repeat(`0`, maxLength - num.toString().length) + num;
    
    //使用新的性能API能夠得到更好的精度(若是可用)
    const timer = typeof performance !== `undefined` && typeof performance.now === `function` ? performance : Date;
    
    function createLogger(options = {}) {
      return ({ getState }) => (next) => (action) => {
        const {
          level, //級別
          logger, //console的API
          collapsed, //
          predicate, //logger的條件
          duration = false, //打印每一個action的持續時間
          timestamp = true, //打印每一個action的時間戳
          transformer = state => state, //在打印以前轉換state
          actionTransformer = actn => actn, //在打印以前轉換action
        } = options;
    
        const console = logger || window.console;
    
        // 若是控制檯未定義則退出
        if (typeof console === `undefined`) {
          return next(action);
        }
    
        // 若是謂詞函數返回false,則退出
        if (typeof predicate === `function` && !predicate(getState, action)) {
          return next(action);
        }
    
        const started = timer.now();
        const prevState = transformer(getState());
    
        const returnValue = next(action);
        const took = timer.now() - started;
    
        const nextState = transformer(getState());
    
        // 格式化
        const time = new Date();
        const isCollapsed = (typeof collapsed === `function`) ? collapsed(getState, action) : collapsed;
    
        const formattedTime = timestamp ? ` @ ${pad(time.getHours(), 2)}:${pad(time.getMinutes(), 2)}:${pad(time.getSeconds(), 2)}.${pad(time.getMilliseconds(), 3)}` : ``;
        const formattedDuration = duration ? ` in ${took.toFixed(2)} ms` : ``;
        const formattedAction = actionTransformer(action);
        const message = `action ${formattedAction.type}${formattedTime}${formattedDuration}`;
        const startMessage = isCollapsed ? console.groupCollapsed : console.group;
    
        // 渲染
        try {
          startMessage.call(console, message);
        } catch (e) {
          console.log(message);
        }
    
        if (level) {
          console[level](`%c prev state`, `color: #9E9E9E; font-weight: bold`, prevState);
          console[level](`%c action`, `color: #03A9F4; font-weight: bold`, formattedAction);
          console[level](`%c next state`, `color: #4CAF50; font-weight: bold`, nextState);
        } else {
          console.log(`%c prev state`, `color: #9E9E9E; font-weight: bold`, prevState);
          console.log(`%c action`, `color: #03A9F4; font-weight: bold`, formattedAction);
          console.log(`%c next state`, `color: #4CAF50; font-weight: bold`, nextState);
        }
    
        try {
          console.groupEnd();
        } catch (e) {
          console.log(`—— log end ——`);
        }
    
        return returnValue;
      };
    }
    
    export default createLogger;複製代碼

解析redux-logger

一、入口函數createLogger(options = {}) 咱們在redux配置文件中調用的就是這個函數,也是redux-logger中惟一一個函數,它只有一個參數option,option是object。

二、return ({ getState }) => (next) => (action) => {} 這行代碼看起來很複雜,一堆的箭頭函數,其實很簡單,createLogger()必定會有一個返回值,可是,咱們在控制檯打印action信息的時候,須要獲取state和action的信息,因此,首先傳入getState方法,getState是redux提供的一個方法,用來獲取store的state。而後再傳入next方法,接着傳入action方法。next和action都是redux提供的方法,到這一步,咱們就把須要的參數都傳入到函數中,能夠進行下一步操做了。

三、定義option的配置參數 咱們在使用redux-logger的時候,習慣了不配置任何參數,直接調用createLogger(),使用默認的配置。但其實還能夠手動傳入一個option配置,不過並不經常使用。

const {
          level, //級別
          logger, //console的API
          collapsed, //
          predicate, //logger的條件
          duration = false, //打印每一個action的持續時間
          timestamp = true, //打印每一個action的時間戳
          transformer = state => state, //在打印以前轉換state
          actionTransformer = actn => actn, //在打印以前轉換action
        } = options;複製代碼

四、定義console 若是你給option配置了console相關的API,那麼就使用你的配置,若是沒有配置,就使用window.console

const console = logger || window.console;複製代碼

五、添加2個異常狀況作退出處理 第一個if語句是控制檯未定義就返回下一個action操做,可是我想不到在瀏覽器中會出現console方法不存在的狀況。 第二個if語句的predicate表示warn、log、error等屬於console的方法。&&表示2個條件要同時知足才執行下面的操做。predicate(getState, action)其實就是相似console.log(getState, action)

// 若是控制檯未定義則退出
        if (typeof console === `undefined`) {
          return next(action);
        }
    
        // 若是謂詞函數返回false,則退出
        if (typeof predicate === `function` && !predicate(getState, action)) {
          return next(action);
        }複製代碼

六、給各個常量賦值 爲何會有這麼多常量呢?咱們來看一張圖,圖上展現了須要打印的各類信息。

總結出來就是:

action action.type @ timer
prev state {}
action {}
next state {}

這裏須要的是action.type, timer, 各類狀態下的state

const started = timer.now();
    const prevState = transformer(getState());
    
    const returnValue = next(action);
    const took = timer.now() - started;
    
    const nextState = transformer(getState());
    
    // 格式化
    const time = new Date();
    const isCollapsed = (typeof collapsed === `function`) ? collapsed(getState, action) : collapsed;
    
    const formattedTime = timestamp ? ` @ ${pad(time.getHours(), 2)}:${pad(time.getMinutes(), 2)}:${pad(time.getSeconds(), 2)}.${pad(time.getMilliseconds(), 3)}` : ``;
    const formattedDuration = duration ? ` in ${took.toFixed(2)} ms` : ``;
    const formattedAction = actionTransformer(action);
    const message = `action ${formattedAction.type}${formattedTime}${formattedDuration}`;
    const startMessage = isCollapsed ? console.groupCollapsed : console.group;複製代碼

上面代碼信息量比較大,咱們還能夠拆分出來看看。

a、先獲取一個開始時間started,而後讀取state,這個state是以前的狀態prevState。returnValue是返回值,返回下一個action。took是你執行完前面3行代碼以後的真實時間,在這裏由於沒有用到異步處理,因此我暫且認爲transformer()和next()是同步的。nextState是新的state。

這段代碼概括起來看就是先讀取開始時間,而後讀取state,這個state由於還有更新action,因此是舊的state,而後執行next傳入新的action,更新完成以後,獲取結束時間,計算更新action的時間差,而後再獲取更新後的state。

const started = timer.now();
    const prevState = transformer(getState());        
    const returnValue = next(action);
    const took = timer.now() - started;
    const nextState = transformer(getState());複製代碼

b、下面的代碼作了一件事情,設置打印的信息。

formattedTime是打印出來的時間,格式是 時:分:秒,formattedDuration是時間差,formattedAction是當前的action方法。isCollapsed用處不大,無論他。

// 格式化
    const time = new Date();
    const isCollapsed = (typeof collapsed === `function`) ? collapsed(getState, action) : collapsed;
    
    const formattedTime = timestamp ? ` @ ${pad(time.getHours(), 2)}:${pad(time.getMinutes(), 2)}:${pad(time.getSeconds(), 2)}.${pad(time.getMilliseconds(), 3)}` : ``;
    const formattedDuration = duration ? ` in ${took.toFixed(2)} ms` : ``;
    const formattedAction = actionTransformer(action);
    const message = `action ${formattedAction.type}${formattedTime}${formattedDuration}`;
    const startMessage = isCollapsed ? console.groupCollapsed : console.group;複製代碼

這幾行代碼作的事情也很是簡單,給須要打印的常量賦值。而後組合以後賦值給message:

const message = `action ${formattedAction.type}${formattedTime}${formattedDuration}`;複製代碼

message == action action.type @ time

七、try {} catch() {} 部分通常不會用到,也能夠無論。

startMessage.call(console, message);表示將message當作參數傳入startMessage,call的第一個參數是指運行環境,意思就是在console打印message信息。

try {
      startMessage.call(console, message);
    } catch (e) {
      console.log(message);
    }複製代碼

八、打印console的信息,這就圖上打印出來的部分了。

由於咱們一般沒有配置level,因此執行的是else語句的操做。

if (level) {
          console[level](`%c prev state`, `color: #9E9E9E; font-weight: bold`, prevState);
          console[level](`%c action`, `color: #03A9F4; font-weight: bold`, formattedAction);
          console[level](`%c next state`, `color: #4CAF50; font-weight: bold`, nextState);
        } else {
          console.log(`%c prev state`, `color: #9E9E9E; font-weight: bold`, prevState);
          console.log(`%c action`, `color: #03A9F4; font-weight: bold`, formattedAction);
          console.log(`%c next state`, `color: #4CAF50; font-weight: bold`, nextState);
        }複製代碼

九、遊戲結束

try {
          console.groupEnd();
        } catch (e) {
          console.log(`—— log end ——`);
        }複製代碼

十、返回值

return returnValue;複製代碼

總結

redux-logger作的事情是在控制檯輸出action的信息,因此首先要獲取前一個action,當前action,而後是下一個action。看完以後,你對redux-logger源碼的理解加深了嗎?





在react開發中,一部分人使用redux-thunk,一部分人使用redux-saga,彼此各有優勢。

今天咱們來研究一下redux-thunk的源碼,看看它到底作了什麼事情。

使用場景

import { createStore, applyMiddleware } from 'redux';
    import thunk from 'redux-thunk';
    import rootReducer from './reducers/index';
    //註冊thunk到applyMiddleware
    const createStoreWithMiddleware = applyMiddleware(
      thunk
    )(createStore);
    
    const store = createStoreWithMiddleware(rootReducer);
    
    //action方法
    function increment() {
      return {
        type: INCREMENT_COUNTER
      };
    }
    //執行一個異步的dispatch
    function incrementAsync() {
      return dispatch => {
        setTimeout(() => {
          dispatch(increment());
        }, 1000);
      };
    }複製代碼

主要代碼:

一、導入thunk

import thunk from 'redux-thunk';複製代碼

二、添加到applyMiddleware()

const createStoreWithMiddleware = applyMiddleware(
      thunk
    )(createStore);複製代碼

咱們能夠猜想thunk是一個object。

redux-thunk源碼

function createThunkMiddleware(extraArgument) {
      return ({ dispatch, getState }) => next => action => {
        if (typeof action === 'function') {
          return action(dispatch, getState, extraArgument);
        }
    
        return next(action);
      };
    }
    
    const thunk = createThunkMiddleware();
    thunk.withExtraArgument = createThunkMiddleware;
    
    export default thunk;複製代碼

一共11行,簡潔,超簡潔,5K+ star。

源碼分析

一、定義了createThunkMiddleware()方法,能夠傳入參數extraArgument。

function createThunkMiddleware(extraArgument){}複製代碼

二、該方法返回的是一個action對象。

咱們知道action自己是一個object,帶有type和arguments。咱們將dispatch和getState傳入action,next()和action()是redux提供的方法。接着作判斷,若是action是一個function,就返回action(dispatch, getState, extraArgument),不然返回next(action)。

return ({ dispatch, getState }) => next => action => {
        if (typeof action === 'function') {
          return action(dispatch, getState, extraArgument);
        }
    
        return next(action);
      };複製代碼

三、執行createThunkMiddleware()

這一步的常量thunk是一個對象,相似{type: "", arg1, arg2, ...}

const thunk = createThunkMiddleware();複製代碼

四、給thunk設置一個變量withExtraArgument,而且將createThunkMiddleware整個函數賦給它。

thunk.withExtraArgument = createThunkMiddleware;複製代碼

五、最後導出thunk。

export default thunk;複製代碼

總結

什麼是thunk?thunk是一箇中間函數,它的返回值是一個表達式。action裏面可能傳遞多個參數,咱們不可能再專門替每一個action寫一個傳遞方法。那麼就有了thunk的出現,thunk能夠將多個參數的函數做爲一個參數傳遞。

例若有這樣一個action,帶有多個參數:

function test(arg1, arg2, ...) {
        return {
            type: "TEST",
            arg1,
            arg2,
            ...
        }
    }複製代碼

而後咱們執行dispatch()方法,咱們須要把test()函數做爲一個參數傳遞。這樣就解決了多參數傳遞的問題,這個test()就成了一個thunk。

若是你對redux-thunk還有疑問,能夠查看這個解釋:redux-thunk of stackoverflow

相關文章
相關標籤/搜索