网上已经有不少react源码分析文档,但都是分析主流程和主要功能函数,没有一个是从reactDOM.render()入口开始分析源码把流程走通尤其是把复杂重要的细节环节走通直到把组件template编译插入网页生效。

react源码设计太复杂,react的缺点就是把事情搞太复杂了,相比之下vue用简单的编程方法也能实现同样的功能甚至更多的功能,可以说是后起之秀超越了前辈,个人设计超过了专业团队设计。

react源码分布在几十个文件中,找函数代码要在这批文件中搜索出来,这无所谓,其中最重要的源码文件如下:

React.js

ReactClass.js

ReactElement.js

ReactDOM.js

ReactMount.js

instantiateReactComponent.js

ReactComponent.js

ReactDOMComponent.js

ReactCompositeComponent.js

ReactReconciler.js

ReactUpdates.js

Transaction.js

还有react-redux两个文件:

connect.js

provider.js

首先描述一下测试项目基本环境和细节,测试项目采用minimum项目,只有一个/路由组件,显示<div>hello world</div>,如果ajax从后台获取到数据,则显示从后台获取的一个字符串,组件使用redux的store数据。

react项目入口代码:
ReactDOM.render(
<Provider store={store}>
<Router history={history} children={routes} />
</Provider>,
MOUNT_NODE

路由routes.js代码:

import { injectReducer } from 'REDUCER'
import createContainer from 'UTIL/createContainer'

const connectComponent = createContainer(
  ({ userData, msg }) => ({ userData, msg }),
  require('ACTION/msg').default
)

export default {
  path: '/',

  getComponent (nextState, cb) {
    require.ensure([], (require) => {
      injectReducer('msg', require('REDUCER/msg/').default)
        cb(null, connectComponent(require('COMPONENT/App').default))
      }, 'App')
    }
}

/路由组件App.js代码:

import React, { Component } from 'react'

export default class App extends Component {
componentWillMount () {
  let _this = this
  setTimeout(function() {
    _this.props.fetchMsg()
  }, 2000)
}
componentWillReceiveProps (nextProps) {
  console.log(this)
}
render () {
  return (
    <div>{ this.props.msg.msgs[0] ? this.props.msg.msgs[0].content : 'hello world' }</div>
  )
}
}

react项目入口方法是:
ReactDOM.render(根组件template)

相当于vue 1.0的router.start(app)或vue 2.0的new Vue(app)。

ReactDOM.render方法代码:
function (nextElement, container, callback) {
  return ReactMount._renderSubtreeIntoContainer(null, nextElement, container, callback);
},

_renderSubtreeIntoContainer: function (parentComponent, nextElement, container, callback) {
  var nextWrappedElement = ReactElement(TopLevelWrapper, null, null, null, null, null, nextElement); // 构造一个Element,第一次是构造toplevel Element
  //React.createElement创建ReactElement对象(vnode),含有type,key,ref,props属性,这个过程中会调用getInitialState()初始化state。
  var component = ReactMount._renderNewRootComponent(nextWrappedElement, container, shouldReuseMarkup, nextContext)._renderedComponent.getPublicInstance();
  return component;

产生的component就是根组件template中第一个标签provider组件实例,里面有层层子节点,有/路由组件实例,有props属性。
那么是从这里开始层层递归到/路由组件节点的。

_renderNewRootComponent: function (nextElement, container, shouldReuseMarkup, context) {
  //nextelement就是根元素节点provider组件元素
  var componentInstance = instantiateReactComponent(nextElement, false); //element -> instance

    function instantiateReactComponent(node, shouldHaveDebugID) { //这个方法是根据Element产生warpper instance,再执行instance.mountComponent开始编译组件节点
      //instance = new ReactCompositeComponentWrapper(element); //这句相当于是下面一句
      instance = new this.construct(element); // 这是instance的构造函数,就是设置一些属性,很普通
        var ReactCompositeComponentMixin = { // 这是instace构造函数代码
          construct: function (element) {
            this.xxx = yyy;
          }
        }
      return instance;
    //instantiateReactComponent根据ReactElement的type分别创建ReactDOMComponent, ReactCompositeComponent,ReactDOMTextComponent等对象,

    //不同的对象instance有不同的mountComponent方法,所以react源码文件有无数个mountComponent函数,其实html元素节点可以不当做component处理
    //instantiateReactComponent被调用执行多次,每次被调用就处理一个元素节点产生一个instance,在本例minimum项目中,产生如下instance:
_instance:TopLevelWrapper
_instance:Provider // provide节点
_instance:Constructor // router节点
_instance:Constructor
_instance:Connect // /路由组件外层
_instance:App //这是/路由组件实例


  ReactUpdates.batchedUpdates(batchedMountComponentIntoNode, componentInstance, container, shouldReuseMarkup, context); //从根元素provider开始编译处理

至此处理结束,根组件template已经插入网页生效,已经从根元素递归处理到最底层元素,所以处理root元素很简单,复杂在于从root元素递归处理子节点再层层返回。

batchedUpdates会调用batchedMountComponentIntoNode,从这个函数开始追踪:

function batchedMountComponentIntoNode(componentInstance, container, shouldReuseMarkup, context) { //从根元素provider开始
  //batchedMountComponentIntoNode以transaction事务的形式调用mountComponentIntoNode
  transaction.perform(mountComponentIntoNode, null, componentInstance, container, transaction, shouldReuseMarkup, context);

    function mountComponentIntoNode(wrapperInstance, container, transaction, shouldReu //从根元素provider开始
      var markup = ReactReconciler.mountComponent(wrapperInstance, transaction, //mountComponent相当于compile,从根节点开始编译
      //Reconciler.mountComponent就是执行instance里面的mountComponent,在执行performInitialMount时会递归调用自己。
      //mountComponent的目的是解析得到每个节点的HTML代码(最后插入网页生效),react叫做markup,是类似vue的vnode对象。

        Reconciler.mountComponent: function (internalInstance, transaction, 
          var markup = internalInstance.mountComponent(transaction, hostParent, hostContainerInfo, context, parentDebugID);
          //对于provider这样的控制组件标签来说,产生的html代码就是一个不可见的comment文本
          return markup;
          // internalInstance是不同的component实例,最典型的component节点类型是html元素节点比如<div>和组件元素节点比如<App>

        Reconciler.mountComponent会递归调用自己完成从根元素递归到最底层元素<div>,是react源码的最核心最关键最牛的代码,因为前端代码要递归html元素tree,这是与后台代码不同的,也是非常复杂的,一旦递归元素tree,就要开始晕菜了,但代码效率巨高,一个递归就完事了,递归也是最体现程序牛的地方,人工智能自我学习肯定也是要用程序递归技术的。

不同的instance有不同的mountComponent方法,我们先来看组件元素的mountComponent方法:

mountComponent: function (transaction, hostParent, hostContainerInfo, context) { //mountComponent其实就是编译节点的意思,react把一切节点视为component
  var publicProps = this._currentElement.props;
  var inst = this._constructComponent(doConstruct, publicProps, publicContext, updateQueue);
  //inst就是new组件实例,那么是在mountComponent阶段初始化组件实例的,new组件实例之后,执行其render()方法就又产生Element,就又需要递归循环element -> instance -> instance.mountComponent -> inst(组件实例) -> render() -> element 如此递归到子节点
    _constructComponent: function (doConstruct, publicProps, publicContext, updateQueue) {
      return this._constructComponentWithoutOwner(doConstruct, publicProps, publicContext, updateQueue);

        _constructComponentWithoutOwner: function (doConstruct, publicProps, publicContext, updateQueue) {
          var Component = this._currentElement.type; //type就是组件构造函数(组件定义代码)
          return new Component(publicProps, publicContext, updateQueue); //App组件外套connect组件,这就是new Connect()组件实例的位置,找到这个位置在分析react源码的道路上就前进了一大步,因为组件定义代码无外乎就是定义一些属性,框架肯定准备了一个组件基类,到时一合并,再new实例,这是js唯一的机制,不可能有其它方法,找到这个位置,再前后去追踪,就能看懂框架到底是如何初始化组件实例的,我们定义的组件代码到底到底是如何被执行的。

  inst.props = publicProps; // double set this.props
  this._instance = inst; //new组件实例保存在instance中,只要执行instance的方法,就可以从this取回inst实例,再执行inst实例里面的render()方法产生一个Element递归下去
  markup = this.performInitialMount(renderedElement, hostParent, hostContainerInfo, transaction, context);
  //inst保存在this实例中,调用performInitialMount时无需传递inst,renderedElement是空的
  return markup; //markup就是编译产生的结果,相当于vnode,含html代码

    performInitialMount: function (renderedElement, hostParent, hostContainerInfo, transaction, context) {

      renderedElement = this._renderValidatedComponent(); //执行inst.render()产生Element,每种组件inst有自己的render方法,provder/router/connect/app组件都有自己的render方法,app的render方法是应用写的,系统组件的render方法都是事先设计好的,比如connect的render方法,还有一个router-context组件
//app的render方法里面是jsx语法,编译时每个节点已经转换为createElement(),所以render方法就是返回一个根元素Element,它里面有多少子元素再递归处理

      var child = this._instantiateReactComponent(renderedElement,    //根据元素类型生成instance
      this._renderedComponent = child;
      var markup = ReactReconciler.mountComponent(child, // 在这里递归调用Reconciler.mountComponent,处理下一个子节点child,是前面根据Element生成的
      return markup;

这是组件component的mountComponet编译方法,再来看html元素component的mountComponent编译方法:

//ReactDOMComponent是针对html元素,在这个minimum项目中,根组件template中只有一个<div>元素节点
ReactDOMComponent.Mixin = {
  mountComponent: function (transaction, hostParent, hostContainerInfo, context) { //只针对<div>执行一次
    var tagContent = this._createContentMarkup(transaction, props, context);

      _createContentMarkup: function (transaction, props, context) {
        var mountImages = this.mountChildren(childrenToUse, transaction, context);

          mountChildren: function (nestedChildren, transaction, context) {
            var children = this._reconcilerInstantiateChildren(nestedChildren, transaction, context);

              _reconcilerInstantiateChildren: function (nestedChildren, transaction, context) {
                return ReactChildReconciler.instantiateChildren(nestedChildren, transaction, context);

                  instantiateChildren: function (nestedChildNodes, transaction, context, ) {
                    return instantiateChild(childInsts, child, name, selfDebugID);

                      function instantiateChild(childInstances, child, name, selfDebugID) {
                        childInstances[name] = instantiateReactComponent(child, true);

再回到mountComponentIntoNode看Reconciler.mountComponent从根元素provider开始递归编译子节点之后再执行什么:

function mountComponentIntoNode(wrapperInstance, container, transaction, shouldReu 
  var markup = ReactReconciler.mountComponent(wrapperInstance, transaction,

  ReactMount._mountImageIntoNode(markup, container, wrapperInstance, shouldReuseMarkup, transaction);
  //这是递归完成之后执行的方法,就是把编译结果插入网页生效,编译结果就是一个<div>字符串</div>,具体的方法无非是用innerHTML/Context方法,或者用appendChild方法,本文忽略

  

至此可以小结一下从根Element开始的处理流程:
Element -> wrapper instance -> instance.mountComponent(compile) -> new instance._currentElement.type()组件实例 -> 组件实例.render()产生Element -> 递归子节点

元素树结构:
provider->router->connect->app

每层元素根据类型创建instance,执行其mountComponent,再new 组件实例inst,再执行组件实例inst的render()方法产生一个element,再递归。

connect的render方法:
Connect.prototype.render = function render() {
this.renderedElement = (0, _react.createElement)(WrappedComponent, this.mergedProps);

当store中的属性变化触发执行connect组件的render方法时,可以看到,它产生的Element是App组件元素,那么递归编译处理就是编译App组件,就是更新App组件,connect是App的代理组件,App组件并没有定义props属性,也没有定义如何更新。

router的render方法:
render: function render(props) {
return _react2.default.createElement(_RouterContext2.default, props);
}
router也会创建一个element,带路由参数。

react源代码重点难点分析的更多相关文章

  1. es6-promise源代码重点难点分析

    摘要 vue和axios都可以使用es6-promise来实现f1().then(f2).then(f3)这样的连写形式,es6-promise其实现代浏览器已经支持,无需加载外部文件.由于promi ...

  2. AXIOS源代码重点难点分析

    摘要 vue使用axios进行http通讯,类似jquery/ajax的作用,类似angular http的作用,axios功能强大,使用方便,是一个优秀的http软件,本文旨在分享axios源代码重 ...

  3. vue 2.0 路由切换以及组件缓存源代码重点难点分析

    摘要 关于vue 2.0源代码分析,已经有不少文档分析功能代码段比如watcher,history,vnode等,但没有一个是分析重点难点的,没有一个是分析大命题的,比如执行router.push之后 ...

  4. apache 重点难点

    apache 重点难点 在于难以理解其工作原理,因为它是c 写的:其模块众多:功能强大而复杂. 其配置也是格式不齐, 比如一下子是 key value , 一下子就成了 xml. 转载: http:/ ...

  5. Android开发重点难点1:RelativeLayout(相对布局)详解

    前言 啦啦啦~博主又推出了一个新的系列啦~ 之前的Android开发系列主要以完成实验的过程为主,经常会综合许多知识来写,所以难免会有知识点的交杂,给人一种混乱的感觉. 所以博主推出“重点难点”系列, ...

  6. React Fiber源码分析 (介绍)

    写了分析源码的文章后, 总觉得缺少了什么, 在这里补一个整体的总结,输出个人的理解~ 文章的系列标题为Fiber源码分析, 那么什么是Fiber,官方给出的解释是: React Fiber是对核心算法 ...

  7. 第213天:12个HTML和CSS必须知道的重点难点问题

    12个HTML和CSS必须知道的重点难点问题 这12个问题,基本上就是HTML和CSS基础中的重点个难点了,也是必须要弄清楚的基本问题,其中定位的绝对定位和相对定位到底相对什么定位?这个还是容易被忽视 ...

  8. 10个HTML和CSS必须知道的重点难点问题

    前端日刊 登录 10个HTML和CSS必须知道的重点难点问题 2018-02-26 阅读 2982 收藏 6 原链:segmentfault.com 分享到:   前端必备图书<深入浅出Node ...

  9. 从源代码的角度分析--在BaseAdapter调用notifyDataSetChanged()之后发生了什么

    导师安排我做一个小项目,其中涉及到利用Adapter作为ListView的适配器,为ListView提供数据.选中某一项后,要让这一项变成选中状态,也就是背景图片要换一下.下面我就用一个小例子来模拟. ...

随机推荐

  1. 201621123040《Java程序设计》第十周学习总结

    1.本周学习总结 2.书面作业 2.1常用异常 2.1.1自己以前编写的代码中经常出现什么异常.需要捕获吗(为什么)?应如何避免? 算术异常ArithmeticException(除数为0的情况) 类 ...

  2. JVM笔记7-内存分配与回收策略

    1.对象优先在Eden分配 大多数情况下,对象在新生代Eden区中分配.当Eden区中没有足够空间分配时,虚拟机将发起一次Minor GC.虚拟机提供了-XX:PrintGCDetails 这个收集器 ...

  3. django启动uwsgi报错

    查看uwsgi.log *** Starting uWSGI 2.0.17 (64bit) on [Thu Apr 5 17:46:15 2018] *** compiled with version ...

  4. Python之旅.第二章数据类型 3.19/3.20/3.21/3.22/3.23

    一.数字类型 1.int类型: 基本使用: 用途:用于年龄,手机号,身份证号: 定义: age=18: 常用操作+内置方法: 正常的运算赋值: 进制转换: print(bin(3)); 把十进制3转换 ...

  5. SQL Server 实现递归查询

    基础数据/表结构                 Sql 语句 ;With cte(id,pid,TName)As ( Select id,pid,TName Union All Select B.i ...

  6. Ansible实战演练

    [root@Ansible-server ~]# rpm -Uvh http://mirrors.ustc.edu.cn/fedora/epel/6/x86_64/epel-release-6-8.n ...

  7. C语言学习(一)

    C语言易学难精,如果在平时的编程中,加入一些小技巧,可以提供程序运行的效率,何乐而不为呢? 本小白初学C语言准备记录自己的学C之路,经常贴一些自己觉得优化的小程序代码,希望大神们不吝 赐教. 宏定义下 ...

  8. apigw鉴权分析(1-5)亚马逊 - 鉴权分析

    一.访问入口 https://developer.amazon.com/public/zh 二.鉴权方式分析 三.分解结论

  9. Spring Security入门(3-2)Spring Security对接用户的权限系统

    源文链接,多谢作者的分享: http://www.360doc.com/content/14/0727/16/18637323_397445724.shtml 1.原生的spring-security ...

  10. sql server 常用的查询语句

    最近在加强sql 语句的学习,整理一下基本语法,现在记录下 select * from dbo.cangku where city='河南' select  distinct(city), cangk ...