React源码的秘密

What is React

A JavaScript library for building user interfaces

基本概念

  • VIRTUAL DOM
    React render执行的结果并不是真正的DOM节点,只是是轻量级的JavaScript对象,我们称之为virtual DOM。虚拟DOM是React的一大亮点,具有batching(批处理)和高效的Diff算法。使用Virtual dom在性能上不会比纯手动优化的DOM操作更快,使用Virtual DOM能在不需要手动优化的情况下,保证过得去的性能,从而提高代码的可维护性和开发效率。这让我们可以无需担心性能问题而随时“刷新”整个页面,由虚拟DOM来确保只对界面上真正变化的节点进行实际的DOM操作。

下图是浏览器的工作流:

a.Create DOM tree:浏览器的渲染引擎根据html的elements,生成Dom node,组成Dom tree
b.Create Render tree:浏览器解析外部CSS文件和元素的inline样式,结合Dom tree的nodes,生成render tree
c.layout:render tree上每一个node生成在屏幕上的具体位置的坐标值
d.painting:绘制

如果直接操作Dom,每次操作都会触发整个渲染流程,如果连续修改50个节点,那就会造成50次重新渲染,这会造成不必要的渲染消耗。我们可以自己编写逻辑,将每个Dom操作汇总到一个Dom fragment,再传递给Dom tree。但这样我们就得自己去记录哪些节点改变,哪些没有改变,这显然开发效率太低,所以我们可以把这个工作抽象一下,于是就有了virtual dom。
virtual dom这个抽象层的作用就是将这件事自动化、抽象化,通过Diff算法计算出需要改变的节点,使用batching批处理多个节点的改动,然后操作Dom树进行渲染,避免不必要的重新渲染。

  • JSX
    JSX just provides syntactic sugar for “React.createElement(component, props, …children)”
    JSX是一个语法糖实际上就是封装了React.createElement,使用JSX可以提高代码的可读性和开发效率
  • Component
    无论是复杂的元素还是简单的元素,都定义为组件。通过组合组件的方式可以构建容易维护、高复用性的组件。
  • one-way reactive data flow
    React 的单向数据流的设计让前端 bug 定位变得简单,页面的UI和数据的对应是唯一的,我们可以通过定位数据变化就可以定位页面展现问题。

React Render Mechanism 渲染机制源码解析

Render analyse

React有3种element,Text element、Basic element 基本元素、Custom element 自定义元素。

Render DOM implement analyse

Basic element 基本元素渲染解析

1
2
3
4
Basic element example:
var element = React.createElement('div',{id:'test',onclick:hello},'click me')
React.render(element,document.getElementById("container"))
  1. 创建虚拟DOM实例
    React.createElement:

use createElement function to create a virtual dom -> React.createElement

  • keep the key to identify element
  • copy the config(attributes) to the props
  • copy the children to the props.children
  • call ReactElement function and send ‘type’,’key’,’props’ for initial a element object

2.将创建好的虚拟DOM实例通过React.render进行渲染
React.render:

  • instantiateReactComponent
    • init component instance
  • mountComponent
    • render component instance to html content

3.在React.render里,首先调用instantiateReactComponent方法,根据element渲染出component实例。

instantiateReactComponent:

  • return ReactDOMTextComponent
    • if(typeof node === ‘string’ || typeof node === ‘number’)
  • return ReactDOMComponent
    • if(typeof node === ‘object’ && typeof node.type === ‘string’)
  • return new ReactCompositeComponent
    • if(typeof node === ‘object’ && typeof node.type === ‘function’)

4.接着调用mountComponent,将instantiateReactComponent得到的component实例渲染成原生的Dom结构

ReactDOMComponent.prototype.mountComponent:

  • assign type : tagOpen = ‘<’ + ‘dic’
  • add props : tagOpen += propKey + props[propKey]
  • recursive child node to be content : each(content += childComponentInstance.mountComponent)
  • result : tagOpen + ‘>’ + content + tagClose

React的伪代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// ReactElement:创建虚拟DOM
function ReactElement(type,key,props){
this.type = type;
this.key = key;
this.props = props;
}
// mountComponent:渲染component,生成的dom结构
ReactDOMTextComponent.prototype.mountComponent = function(rootID) {
this._rootNodeID = rootID;
return '<span data-reactid="' + rootID + '">' + this._currentElement + '</span>';
}
// instantiateReactComponent:生成component
function instantiateReactComponent(node){
......
}
React = {
createElement:function(type,config,children){
...
return new ReactElement(type, key,props);
},
createClass:function(spec){
...
},
// 渲染入口
render:function(element,container){
var componentInstance = instantiateReactComponent(element);
var markup = componentInstance.mountComponent(React.nextReactRootIndex++);
}
}

渲染结果
HelloWorld组件通过React.render进行解析最后变成Render Tree

1
2
3
4
5
<div data-reactid="0">
<span data-reactid="0.0">say:</span>
<span data-reactid="0.1">Hello </span>
<span data-reactid="0.2">John</span>
</div>

渲染流程图

Render Composite implement analyse

Custom element自定义元素渲染解析

自定义元素的渲染流程有两点不一样,第一增加了创建自定义类的环节,第二,在最后一步“mountComponent”会递归调用,将父组件的每一个子组件进行渲染解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var HelloMessage = React.createClass({
getInitialState: function() {
return {type: 'say:'};
},
componentWillMount: function() {
console.log('我就要开始渲染了。。。')
},
componentDidMount: function() {
console.log('我已经渲染好了。。。')
},
render: function() {
return React.createElement("div", null,this.state.type, "Hello ", this.props.name);
}
});
React.render(React.createElement(HelloMessage, {name: "John"}), document.getElementById("container"));

1.创建自定义类
React.createClass

  • Constructor a child class
  • inserit to ReactClasss
    • Constructor.prototype = new ReactClass();
  • extend Constructor.prototype,spec

ReactCompositeComponent.prototype.mountComponent

  • initialize public class by ‘ReactClass’ function
    • var inst = new ReactClass(publicProps);
  • life cycle callback
    • inst.componentWillMount()
  • call render to instance a element
    • renderedElement = inst.render() 返回的可能是一个DOM element
  • getting component instance
    • renderedComponentInstance = instantiateReactComponent(renderedElement)
  • get rendered result by renderedComponentInstance
    • renderedComponentInstance.mountComponent
  • life cycle callback
    • inst.componentDidMount()

React Native ReRender mechanism 重新渲染机制

一个react组件的重新渲染,是通过setState方法的调用,导致stated改变发起的
ReactClass.prototype.setState

  • call ‘this._reactInternalInstance.receiveComponent(null, newState);’

All the component implement the ‘receiveComponent’ the handle the render of themself. the DOMComponent is the most complexest ,it used diff algorithm to handle the child node update.

state改变后会调用组件的receiveComponent方法进行element和属性的更新

ReactCompositeComponent.prototype.receiveComponent

  • update element 更新element对象
  • bind the new state and props 生成心的state和props
  • judge whether update or reRender the element by type and key

ReactDOMTextComponent.prototype.receiveComponent

  • if the text string is change, update content of the node 如果text的内容有变化,刷新节点

ReactDOMComponent.prototype.receiveComponent

  • update element 更新element对象
  • updateDOMProperties 更新属性
  • updateDOMChildren 更新子节点

implementation of DomComponent RecevieComponent

首先是更新Properties

ReactDOMComponent.prototype._updateDOMProperties

  • remove the old attribute
  • remove the event monitor
  • add the new attribute
  • add the new event monitor

接着是更新子组件

ReactDOMComponent.prototype._updateDOMChildren

  • diff
    • user diff algorithm the find out diffrence and add the diffrence to the diffQueue
  • patch
    • traversal diffQueue for removing the changed node , inserting the new note and inserting the modified node

analyse diff and patch

更新子组件时,使用diff算法计算出需要移动、插入、删除的组件,大部分没有变动的组件不做更新。

ReactDOMComponent.prototype._diff

  • getting the previous component of childrend
  • generating the next component of childrend
  • assign the new children
  • compare the previous and next components
    • previous Child handle
      • if these is the same component and element , move it (MOVE_EXISTING)
      • if these is the same component but not the same element, remove element (REMOVE_NODE)
        • remove event monitor of previous child
      • if a previous child which was not exist in nest queue, delete the component
    • next child handle
      • add the new node (INSERT_MARKUP)

ReactDOMComponent.prototype._patch

  • delete REMOVE_NODE nodes
  • delete MOVE_EXISTING nodes
  • insert INSERT_MARKUP nodes
  • insert MOVE_EXISTING nodes

参考

reactjs源码分析-上篇
reactjs源码分析-下篇(更新机制实现原理)
React为什么要使用Virtual DOM
React虚拟DOM浅析
React 介绍
渲染树构建、布局及绘制
浏览器工作原理
How WebKit Works

Blacktea wechat
ex. subscribe to my blog by scanning my public wechat account
记录生活于感悟,您的支持将鼓励我继续创作!