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

相关文章
相关标签/搜索