react-redux原理
react-redux原理分析
写在前面
之前写了一篇分析Redux中Store实现的文章(详见:Redux原理(一):Store实现分析),突然意识到,其实React与Redux并没有什么直接的联系。Redux作为一个通用模块,主要还是用来处理应用中state的变更,而展示层不一定是React。
但当我们希望在React+Redux的项目中将两者结合的更好,可以通过react-redux做连接。
本文结合react-redux的使用,分析其实现原理。
react-redux
react-redux是一个轻量级的封装库,核心方法只有两个:
- Provider
 - connect
 
下面我们来逐个分析其作用
Provider
完整源码请戳这里
Provider模块的功能并不复杂,主要分为以下两点:
- 在原应用组件上包裹一层,使原来整个应用成为Provider的子组件
 - 接收Redux的store作为props,通过context对象传递给子孙组件上的connect
 
下面看下具体代码:
封装原应用
[31-34] render方法中,渲染了其子级元素,使整个应用成为Provider的子组件。
1、this.props.children是react内置在this.props上的对象,用于获取当前组件的所有子组件
2、Children为react内部定义的顶级对象,该对象上封装了一些方便操作子组件的方法。Children.only用于获取仅有的一个子组件,没有或超过一个均会报错。故需要注意:确保Provider组件的直接子级为单个封闭元素,切勿多个组件平行放置。
传递store
[26-29] Provider初始化时,获取到props中的store对象;
[22-24] 将外部的store对象放入context对象中,使子孙组件上的connect可以直接访问到context对象中的store。
1、context可以使子孙组件直接获取父级组件中的数据或方法,而无需一层一层通过props向下传递。context对象相当于一个独立的空间,父组件通过getChildContext()向该空间内写值;定义了contextTypes验证的子孙组件可以通过this.context.xxx,从context对象中读取xxx字段的值。
小结
总而言之,Provider模块的功能很简单,从最外部封装了整个应用,并向connect模块传递store。
而最核心的功能在connect模块中。
connect
正如这个模块的命名,connect模块才是真正连接了React和Redux。
现在,我们可以先回想一下Redux是怎样运作的:首先需要注册一个全局唯一的store对象,用来维护整个应用的state;当要变更state时,我们会dispatch一个action,reducer根据action更新相应的state。
下面我们再考虑一下使用react-redux时,我们做了什么:
import React from "react"
import ReactDOM from "react-dom"
import { bindActionCreators } from "redux"
import {connect} from "react-redux"
class xxxComponent extends React.Component{
    constructor(props){
        super(props)
    }
    componentDidMount(){
        this.props.aActions.xxx1();
    }
    render (
        <div>
            {this.props.$$aProps}
        </div>
    )
}
export default connect(
    state=>{
        return {
            $$aProps:state.$$aProps,
            $$bProps:state.$$bProps,
            // ...
        }
    },
    dispatch=>{
        return {
            aActions:bindActionCreators(AActions,dispatch),
            bActions:bindActionCreators(BActions,dispatch),
            // ...
        }
    }
)(xxxComponent)
通过以上代码,我们可以归纳出以下信息:
1、使用了react-redux后,我们导出的对象不再是原先定义的xxxComponent,而是通过connect包裹后的新React.Component对象。
connect执行后返回一个函数(wrapWithConnect),那么其内部势必形成了闭包。而wrapWithConnect执行后,必须要返回一个ReactComponent对象,才能保证原代码逻辑可以正常运行,而这个ReactComponent对象通过render原组件,形成对原组件的封装。
2、渲染页面需要store tree中的state片段,变更state需要dispatch一个action,而这两部分,都是从this.props获取。故在我们调用connect时,作为参数传入的state和action,便在connect内部进行合并,通过props的方式传递给包裹后的ReactComponent。
好了,以上只是我们的猜测,下面看具体实现,完整代码请戳这里。
connect完整函数声明如下:
connect(
    mapStateToProps(state,ownProps)=>stateProps:Object,
    mapDispatchToProps(dispatch, ownProps)=>dispatchProps:Object,
    mergeProps(stateProps, dispatchProps, ownProps)=>props:Object,
    options:Object
)=>(
    component
)=>component
再来看下connect函数体结构,我们摘取核心步骤进行描述
export default function connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {}) {
    // 参数处理
    // ...
    return function wrapWithConnect(WrappedComponent) {
        class Connect extends Component {
            constructor(props, context) {
                super(props, context)
                this.store = props.store || context.store;
                const storeState = this.store.getState()
                this.state = { storeState }
            }
            // 周期方法及操作方法
            // ...
            render(){
                this.renderedElement = createElement(WrappedComponent,
                    this.mergedProps //mearge stateProps, dispatchProps, props
                )
                return this.renderedElement;
            }
        }
        return hoistStatics(Connect, WrappedComponent);
    }
}
其实已经基本印证了我们的猜测:
1、connect通过context获取Provider中的store,通过store.getState()获取整个store tree 上所有state。
2、connect模块的返回值wrapWithConnect为function。
3、wrapWithConnect返回一个ReactComponent对象Connect,Connect重新render外部传入的原组件WrappedComponent,并把connect中传入的mapStateToProps, mapDispatchToProps与组件上原有的props合并后,通过属性的方式传给WrappedComponent。
下面我们结合代码进行分析一下每个函数的意义。
mapStateToProps
mapStateToProps(state,props)必须是一个函数。
参数state为store tree中所有state,参数props为通过组件Connect传入的props。
返回值表示需要merge进props中的state。
以上代码用来计算待merge的state,[104-105]通过调用finalMapStateToProps获取merge state。其中作为参数的state通过store.getState()获取,很明显是store tree中所有的state
mapDispatchToProps
mapDispatchToProps(dispatch, props)可以是一个函数,也可以是一个对象。
参数dispatch为store.dispatch()函数,参数props为通过组件Connect传入的props。
返回值表示需要merge进props中的action。
以上代码用来计算待merge的action,代码逻辑与计算state十分相似。作为参数的dispatch就是store.dispatch。
mergeProps
mergeProps是一个函数,定义了mapState,mapDispatch及this.props的合并规则,默认合并规则如下:
需要注意的是:如果三个对象中字段出现同名,前者会被后者覆盖
如果通过connect注册了mergeProps方法,以上代码会使用mergeProps定义的规则进行合并,mergeProps合并后的结果,会通过props传入Connect组件。
options
options是一个对象,包含pure和withRef两个属性
pure
表示是否开启pure优化,默认值为true
withRef
withRef用来给包装在里面的组件一个ref,可以通过getWrappedInstance方法来获取这个ref,默认为false。
React如何响应store变化
文章一开始我们也提到React其实跟Redux没有直接联系,也就是说,Redux中dispatch触发store tree中state变化,并不会导致React重新渲染。
react-redux才是真正触发React重新渲染的模块,那么这一过程是怎样实现的呢?
刚刚提到,connect模块返回一个wrapWithConnect函数,wrapWithConnect函数中又返回了一个Connect组件。Connect组件的功能有以下两点:
1、包装原组件,将state和action通过props的方式传入到原组件内部
2、监听store tree变化,使其包装的原组件可以响应state变化
下面我们主要分析下第二点:
如何注册监听
Redux中,可以通过store.subscribe(listener)注册一个监听器。listener会在store tree更新后执行。
以上代码为Connect组件内部,向store tree注册listener的过程。
[199] 调用store.subscribe注册一个名为handleChange的listener,返回值为当前listener的注销函数。
何时注册

可以看到,当Connect组件加载到页面后,当前组件开始监听store tree变化。
何时注销
当当前Connect组件销毁后,我们希望其中注册的listener也一并销毁,避免性能问题。此时可以在Connect的componentWillUnmount周期函数中执行这一过程。
变更处理逻辑
有了触发组件更新的时机,我们下面主要看下,组件是通过何种方式触发重新渲染
[244-245] Connect组件在初始化时,就已经在this.state中缓存了store tree中state的状态。这两行分别取出当前state状态和变更前state状态进行比较
[262] 比较过程暂时略过,这一行将最终store tree中state通过this.setState()更新到Connect内部的state中,而this.setState()方法正好可以触发Connect及其子组件的重新渲染。
小结
可以看到,react-redux的核心功能都在connect模块中,理解好这个模块,有助于我们更好的使用react-redux处理业务问题,优化代码性能。
总结
本文通过分析react-redux源码,详细介绍了Provider和connect模块,重新梳理了Reat、redux、react-redux三者间的关系。
个人觉得多看看源码还是很有好处的,一方面可以加深自己对已使用框架的理解;再一方面可以学到一些优秀的编程思路。
技术这条路上,懂的越多,不懂的也就越多,学无止境,戒骄戒躁。
react-redux原理的更多相关文章
- React + Redux 入坑指南
		
Redux 原理 1. 单一数据源 all states ==>Store 随着组件的复杂度上升(包括交互逻辑和业务逻辑),数据来源逐渐混乱,导致组件内部数据调用十分复杂,会产生数据冗余或者混用 ...
 - ReactJS React+Redux+Router+antDesign通用高效率开发模板,夜间模式为例
		
工作比较忙,一直没有时间总结下最近学习的一些东西,为了方便前端开发,我使用React+Redux+Router+antDesign总结了一个通用的模板,这个技术栈在前端开发者中是非常常见的. 总的来说 ...
 - 详解react/redux的服务端渲染:页面性能与SEO
		
亟待解决的疑问 为什么服务端渲染首屏渲染快?(对比客户端首屏渲染) react客户端渲染的一大痛点就是首屏渲染速度慢问题,因为react是一个单页面应用,大多数的资源需要在首次渲染前就加载 ...
 - React+Redux实现追书神器网页版
		
引言 由于现在做的react-native项目没有使用到redux等框架,写了一段时间想深入学习react,有个想法想做个demo练手下,那时候其实还没想好要做哪一个类型的,也看了些动漫的,小说阅读, ...
 - 实例讲解基于 React+Redux 的前端开发流程
		
原文地址:https://segmentfault.com/a/1190000005356568 前言:在当下的前端界,react 和 redux 发展得如火如荼,react 在 github 的 s ...
 - 【redux】详解react/redux的服务端渲染:页面性能与SEO
		
亟待解决的疑问 为什么服务端渲染首屏渲染快?(对比客户端首屏渲染) react客户端渲染的一大痛点就是首屏渲染速度慢问题,因为react是一个单页面应用,大多数的资源需要在首次渲染前就加载 ...
 - react案例->新闻移动客户端--(react+redux+es6+webpack+es6的spa应用)
		
今天分享一个react应用,应在第一篇作品中说要做一个react+redux+xxx的应用.已经做完一部分,拿出来分享.github地址为:点我就可以咯~ 这里实现了一个新闻移动站的spa.本来想写p ...
 - 轻松理解Redux原理及工作流程
		
轻松理解Redux原理及工作流程 Redux由Dan Abramov在2015年创建的科技术语.是受2014年Facebook的Flux架构以及函数式编程语言Elm启发.很快,Redux因其简单易学体 ...
 - react + redux 完整的项目,同时写一下个人感悟
		
先附上项目源码地址和原文章地址:https://github.com/bailicangd... 做React需要会什么? react的功能其实很单一,主要负责渲染的功能,现有的框架,比如angula ...
 - Immutable.js 以及在 react+redux 项目中的实践
		
来自一位美团大牛的分享,相信可以帮助到你. 原文链接:https://juejin.im/post/5948985ea0bb9f006bed7472?utm_source=tuicool&ut ...
 
随机推荐
- DEDECMS5.7 首页和栏目页调用文章按权重排序
			
dedecms 5.7版本已增加按权重排序功能: [arclist]标签增加按权重排序,在后台管理DEDE里找到以下目录\include\taglib中的arclist.lib.php文件并打开 大约 ...
 - 45个必备的实用jQuery代码段[转载]
			
1. 如何创建嵌套的过滤器: //允许你减少集合中的匹配元素的过滤器, //只剩下那些与给定的选择器匹配的部分.在这种情况下, //查询删除了任何没(:not)有(:has) //包含class为“s ...
 - Mina学习之与Spring整合
			
本章中演示在Spring中整合Mina,为了整合到Spring,需要做以下几个步骤: 1. 设置IoHandler <bean id="trapHandler" class= ...
 - winform datetimepacker 开始日期 结束日期                                                    分类:            WinForm             2014-07-15 19:14    124人阅读    评论(0)    收藏
			
dtpStart;//开始日期 dtpEnd;//结束日期 1:开始日期小于结束日期 加载dtpEnd的ValueChanged事件即可. //开始日期小于结束日期 private v ...
 - DataView操作DataTable
			
1.DataView筛选数据 //假设有一个DataTable数据 DataTable dt = new DataTable(); //转成DefaultView DataView dv = dt.D ...
 - ftp不能上传解决办法
			
自己的服务器,然后我分好ftp,有一天,有个客户要传东西,发现怎么也传不上,但是可以下载,,,,弄了半天,目录权限也是完全访问,但还是不行,原来是serv-u分配的空间小了啊,所以只能下载不能上传.. ...
 - mksquash_lzma-3.2 编译调试记录
			
今天在编译mksquash_lzma-3.2的时候出现了如下问题: /home/test/RT288x_SDK/toolchain/mksquash_lzma-3.2/lzma443/C/7zip/C ...
 - IIS部署.NET项目的有关事项_2015.07.02
			
今天在做项目中的关于发送邮件的一些功能.在部署服务的时候遇到了一些奇葩的问题,基本上是和IIS有关的问题. 首先,项目是基于.NET Framework4.0 版本的,由于本人用的是一台新配置好的机器 ...
 - SGU 121.Bridges painting
			
原题地址 题意: 新百慕大由N个岛屿组成,在岛屿之间有一些连接它们的桥.在任意两个岛屿之间,最多只有一座桥连接它们.总统先生下达命令,要求给所有桥上色. 每一座桥能被染成 白色 或者 黑色. 每一个岛 ...
 - 将fastjson元素转化为String[]
			
在fastjson中如果JSONObject中添加了 String[] 类型的元素 例如 JSONObject jo = new JSONObject(); String[] array = {&qu ...