react-redux简介

redux是一个数据管理框架,而react-redux是专门针对react开发的一个插件。react-redux提供了2个API,Provider和connect。本来打算在一篇文章同时讲解2个API的实现,不过看了一下connect的源码,368行,还是分开解析吧。

本文带领大家分析Provider的核心代码。

如何使用Provider

我们先了解在react项目中是如何使用Provider。

import { Provider } from 'react-redux';import configureStore from './store/configureStore';const store = configureStore();ReactDOM.render((<Provider store={store}></Provider>),document.getElementById('root'));复制代码

上面的代码可以看出,使用Provider分为下面几个步骤:

1、导入Provider 这里跟小白分享一个小知识,你可以看到Provider加了个大括号,而第二个import configureStore没有加大括号,这是因为react-redux的文件中没有指定default输出。如果指定了export default,则不需要加大括号,注意一个js文件只能有一个default。

import { Provider } from 'react-redux';复制代码

2、将store作为参数传入Provider。

<Provider store={store}></Provider>复制代码

Provider源码

import { Component, Children } from 'react'import PropTypes from 'prop-types'import storeShape from '../utils/storeShape'import warning from '../utils/warning'let didWarnAboutReceivingStore = falsefunction warnAboutReceivingStore() {if (didWarnAboutReceivingStore) {return}didWarnAboutReceivingStore = truewarning('<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 } = thisconst { store: nextStore } = nextPropsif (store !== nextStore) {warnAboutReceivingStore()}}}Provider.propTypes = {store: storeShape.isRequired,children: PropTypes.element.isRequired}Provider.childContextTypes = {store: storeShape.isRequired}复制代码

Provider源码解析

Provider只有一个参数,非常简单,代码也仅有55行。

1、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的元素。

2、给Provider组件设置propTypes验证。storeShape是一个封装的方法。

Provider.propTypes = {store: storeShape.isRequired,children: PropTypes.element.isRequired}//storeShapeimport PropTypes from 'prop-types'export default PropTypes.shape({subscribe: PropTypes.func.isRequired,dispatch: PropTypes.func.isRequired,getState: PropTypes.func.isRequired})复制代码

3、验证childContextTypes 它的作用就是让Provider下面的子组件能够访问到store。 详细解释和用法看 react关于context的介绍

Provider.childContextTypes = {store: storeShape.isRequired}复制代码

4、node运行环境判断 如果不是生产环境,也就是在开发环境中,实现componentWillReceiveProps()。

if (process.env.NODE_ENV !== 'production') {Provider.prototype.componentWillReceiveProps = function (nextProps) {const { store } = thisconst { store: nextStore } = nextPropsif (store !== nextStore) {warnAboutReceivingStore()}}}复制代码

其实也可以把这段代码写到Provider组件内部去。

他的作用是当接收到新的props的时候,如果是在开发环境下,就判断当前的store和下一个store是不是不相等,如果是,就执行warnAboutReceivingStore()。

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

5、warnAboutReceivingStore的作用。 上面说到执行了warnAboutReceivingStore,那么warnAboutReceivingStore的作用是什么呢?

let didWarnAboutReceivingStore = falsefunction warnAboutReceivingStore() {if (didWarnAboutReceivingStore) {return}didWarnAboutReceivingStore = truewarning('<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分为下面几个步骤:

1、导入redux-logger

import createLogger from 'redux-logger';复制代码

2、运行createLogger方法,将返回结果赋值给常量

const logger = createLogger();复制代码

3、将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的APIcollapsed, //predicate, //logger的条件duration = false, //打印每个action的持续时间timestamp = true, //打印每个action的时间戳transformer = state => state, //在打印之前转换stateactionTransformer = 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

1、入口函数createLogger(options = {}) 我们在redux配置文件中调用的就是这个函数,也是redux-logger中唯一一个函数,它只有一个参数option,option是object。

2、return ({ getState }) => (next) => (action) => {} 这行代码看起来很复杂,一堆的箭头函数,其实很简单,createLogger()一定会有一个返回值,但是,我们在控制台打印action信息的时候,需要获取state和action的信息,所以,首先传入getState方法,getState是redux提供的一个方法,用来获取store的state。然后再传入next方法,接着传入action方法。next和action都是redux提供的方法,到这一步,我们就把需要的参数都传入到函数中,可以进行下一步操作了。

3、定义option的配置参数 我们在使用redux-logger的时候,习惯了不配置任何参数,直接调用createLogger(),使用默认的配置。但其实还可以手动传入一个option配置,不过并不常用。

const {level, //级别logger, //console的APIcollapsed, //predicate, //logger的条件duration = false, //打印每个action的持续时间timestamp = true, //打印每个action的时间戳transformer = state => state, //在打印之前转换stateactionTransformer = actn => actn, //在打印之前转换action} = options;复制代码

4、定义console 如果你给option配置了console相关的API,那么就使用你的配置,如果没有配置,就使用window.console

const console = logger || window.console;复制代码

5、添加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);}复制代码

6、给各个常量赋值 为什么会有这么多常量呢?我们来看一张图,图上展示了需要打印的各种信息。

总结出来就是:

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

7、try {} catch() {} 部分一般不会用到,也可以不管。

startMessage.call(console, message);表示将message当做参数传入startMessage,call的第一个参数是指运行环境,意思就是在console打印message信息。

try {startMessage.call(console, message);} catch (e) {console.log(message);}复制代码

8、打印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);}复制代码

9、游戏结束

try {console.groupEnd();} catch (e) {console.log(`—— log end ——`);}复制代码

10、返回值

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到applyMiddlewareconst createStoreWithMiddleware = applyMiddleware(thunk)(createStore);const store = createStoreWithMiddleware(rootReducer);//action方法function increment() {return {type: INCREMENT_COUNTER};}//执行一个异步的dispatchfunction incrementAsync() {return dispatch => {setTimeout(() => {dispatch(increment());}, 1000);};}复制代码

主要代码:

1、导入thunk

import thunk from 'redux-thunk';复制代码

2、添加到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。

源码分析

1、定义了createThunkMiddleware()方法,可以传入参数extraArgument。

function createThunkMiddleware(extraArgument){}复制代码

2、该方法返回的是一个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);};复制代码

3、执行createThunkMiddleware()

这一步的常量thunk是一个对象,类似{type: "", arg1, arg2, ...}

const thunk = createThunkMiddleware();复制代码

4、给thunk设置一个变量withExtraArgument,并且将createThunkMiddleware整个函数赋给它。

thunk.withExtraArgument = createThunkMiddleware;复制代码

5、最后导出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

react-redux的Provider和connect相关推荐

  1. React Redux 的一些基本知识点

    一.React.createClass 跟 React.Component 的区别在于后者使用了ES6的语法,用constructor构造器来构造默认的属性和状态. 1. React.createCl ...

  2. React+Redux开发实录(一)搭建工程脚手架

    React+Redux开发实录(一)搭建工程脚手架 React+Redux开发实录(二)React技术栈一览 搭建工程脚手架 准备工作 安装node 安装git 安装一款前端IDE 推荐VSCode, ...

  3. react实战项目_React实战之React+Redux实现一个天气预报小项目

    引言 经过一段时间的React学习,React和Vue的开发确实有很大的不同,但是都是MVVM框架,因此上手没有很大的难度,这次用React+Redux开发一个天气预报小项目.源码地址:https:/ ...

  4. react+redux+generation-modation脚手架搭建一个todolist

    TodoList 1. 编写actions.js 2. 分析state 试着拆分成多个reducer 3. 了解store 4. 了解redux数据流生命周期 5. 分析容器组件和展示组件 搞清楚,数 ...

  5. webpack+react+redux+es6开发模式

    一.预备知识 node, npm, react, redux, es6, webpack 二.学习资源 ECMAScript 6入门 React和Redux的连接react-redux Redux 入 ...

  6. 我的第一个 react redux demo

    最近学习react redux,先前看过了几本书和一些博客之类的,感觉还不错,比如<深入浅出react和redux>,<React全栈++Redux+Flux+webpack+Bab ...

  7. node.js学习之react,redux,react-redux

    redux好难懂,终于明白了一点,做了个小demo,记录一下. 先看目录结构: src |--index.js |--actions |--index.js |--components |--Addi ...

  8. React Redux: 从文档看源码 - Components篇

    注:这篇文章只是讲解React Redux这一层,并不包含Redux部分.Redux有计划去学习,等以后学习了Redux源码以后再做分析 注:代码基于现在(2016.12.29)React Redux ...

  9. React+TS学习和使用(三):React Redux和项目的路由配置

    开启学习react+ts,本篇主要是学习使用React Redux和项目的路由配置 一.React Redux 需求:使用TS+React Redux实现一个累加. A. 安装 $ yarn add ...

最新文章

  1. linux 变量替换字符串,变量替换 字符串处理
  2. FFmpeg-20160422-snapshot-bin
  3. Mybatis怎么在mapper中用多个参数
  4. mysql 优化(一)
  5. FIR数字滤波器设计_窗函数法
  6. 【2】信息的表示和处理
  7. java8 streams_使用Java 8 Streams进行编程对算法性能的影响
  8. spring Boot 学习(七、Spring Boot与开发热部署)
  9. html页面左右布局透明背景,HTML透明背景
  10. Laravel Homestead安装笔记
  11. (原)ubuntu下cadvisor+influxdb+grafana+supervisord监控主机和docker的containers
  12. 迷你KMS mini-KMS_Activator_v1.3_Office2010_VL_ENG使用
  13. 一个计算机爱好者的不完整回忆(十二)下载软件
  14. 新美大--软件测试--《社招、校招jd、公司具体介绍、培训发展、关于实习是什么,要求及常见问题、校招行程、校招常见问题》整理
  15. linux轻量级进程,linux轻量级进程LWP
  16. 如何使用Tenderly在Moonbeam上构建更优秀的Dapp
  17. Pentaho Data Integration初步安装
  18. 象棋里的天地炮与重炮
  19. uni-app 上拉加载函数 onReachBottom 不触发
  20. shell中连接符(并且、和、或者)

热门文章

  1. Java锁的种类以及辨析(二):自旋锁的其他种类
  2. 把一台Cisco路由器配置为帧中继交换机
  3. X86中的RDTSC指令
  4. SilverLight学习笔记--Silverlight中WebRequest通讯
  5. php网站安全狗绕过,最新安全狗绕过姿势 - Azeng呐的个人空间 - OSCHINA - 中文开源技术交流社区...
  6. 【python教程入门学习】七夕情人节表白|Python程序员的花式表白
  7. python init文件作用___init__.py 文件的作用
  8. 事务中mybatis通过id查不到但是通过其他条件可以查到_40打卡 MyBatis 学习
  9. c语言统计单词字母个数,C语言统计单词个数
  10. hooks 使用dva_Taro3 中使用dva