How to write a simple react

translated from http://www.html-js.com/article/JS-analysis-of-the-single-row-from-zero-reactjs-source-first-rendering-principle%203154

Thanks to big boss purple bamboo

Lets Start from simple

Shall we render hello world.

let’s see the code:

<script type="text/javascript">
React.render('hello world',document.getElementById("container"))
</script>
/**
对应的html为
<div id="container"></div>

生成后的html为:
<div id="container">
<span data-reactid="0">hello world</span>
</div>
*/

Assuming with this one line, we can render ‘hello world’ to the corresponding div.
What should we do for this:

//component类,用来表示文本在渲染,更新,删除时应该做些什么事情
function ReactDOMTextComponent(text) {
//存下当前的字符串
this._currentElement = '' + text;
//用来标识当前component
this._rootNodeID = null;
}
//component渲染时生成的dom结构
ReactDOMTextComponent.prototype.mountComponent = function(rootID) {
this._rootNodeID = rootID;
return '<span data-reactid="' + rootID + '">' + this._currentElement + '</span>';
}

//component工厂  用来返回一个component实例
function instantiateReactComponent(node){
if(typeof node === 'string' || typeof node === 'number'){
return new ReactDOMTextComponent(node)
}
}

React = {
nextReactRootIndex:0,
render:function(element,container){
        var componentInstance = instantiateReactComponent(element);
var markup = componentInstance.mountComponent(React.nextReactRootIndex++);
$(container).html(markup);
//触发完成mount的事件
$(document).trigger('mountReady'); }
}

There are three part of this:
1. React.render as the entry point responsible for rendering
2. we have component class concept, ReactDOMTextComponent is a component class definition, defines as for this text node, what to do when rendering, updating, deleting, here we just use it to render, let’s just put others away for now.
3. instantiateReactComponent, pur the type of element, now it’s only string, return an instance of component. it’s just a class factory.

nextReactRootIndex is every component’s id, increase 1 continusly, to make it unique, so that we can find the element by the mark.

Here we can see we devide the logic to few part, the main renering logic is in the specific component class. React.render is responsible for the whole dispatching process, here we call instantiateReactComponent to generate a corresponding component type instance, then we use the object’s mountComponent to get the generated content, then we write it to the container node.

Some might ask just these few steps, are there any goodies for these complexity, don’t worry, keep up.

element debut

We know the best of reactjs is its virtural dom definition, we normally use React.createElment to create a virtual dom element, there are two type of v-dom, one is browser element like div p input form, one is custom element.

We need to mention the text node, it’s not v-dom, but for the rendering coherent. it’s just wrap up by span markup span and give it a simple component(translator: the new 0.15 version of react plans to cancel the span markup)

Here we just talk about the basic element.

In reactjs, sometims we want hello world wrapped in div, and have some property

//演示事件监听怎么用
function hello(){
alert('hello')
}

var element = React.createElement('div',{id:'test',onclick:hello},'click me')
React.render(element,document.getElementById("container"))

/**
//生成的html为:
<div data-reactid="0" id="test">
<span data-reactid="0.0">click me</span>
</div>

//点击文字,会弹出hello的对话框
*/

Here we use React.createElement to create a basic element, now let’s see the lite version of React.createElement.

//ReactElement就是虚拟dom的概念,具有一个type属性代表当前的节点类型,还有节点的属性props
//比如对于div这样的节点type就是div,props就是那些attributes
//另外这里的key,可以用来标识这个element,用于优化以后的更新,这里可以先不管,知道有这么个东西就好了
function ReactElement(type,key,props){
this.type = type;
this.key = key;
this.props = props;
}

React = {
nextReactRootIndex:0,
createElement:function(type,config,children){
var props = {},propName;
config = config || {}
//看有没有key,用来标识element的类型,方便以后高效的更新,这里可以先不管
var key = config.key || null;
        //复制config里的内容到props
for (propName in config) {
if (config.hasOwnProperty(propName) && propName !== 'key') {
props[propName] = config[propName];
}
}
        //处理children,全部挂载到props的children属性上
//支持两种写法,如果只有一个参数,直接赋值给children,否则做合并处理
var childrenLength = arguments.length - 2;
if (childrenLength === 1) {
props.children = $.isArray(children) ? children : [children] ;
} else if (childrenLength > 1) {
var childArray = Array(childrenLength);
for (var i = 0; i < childrenLength; i++) {
childArray[i] = arguments[i + 2];
}
props.children = childArray;
}
        return new ReactElement(type, key,props);
    },
render:function(element,container){
var componentInstance = instantiateReactComponent(element);
var markup = componentInstance.mountComponent(React.nextReactRootIndex++);
$(container).html(markup);
//触发完成mount的事件
$(document).trigger('mountReady');
}
}

createElement just do some house-keeping stuff to return the final ReactElement instance, that is, what we called, virtual dom instance.

Here notice the definition of key, it’s used for optimising performance.

OK, here we have element instance, let’s render the shit out, now what render accept is a ReactElement not text, let’s do magic to instantiateReactComponent:

function instantiateReactComponent(node){
//文本节点的情况
if(typeof node === 'string' || typeof node === 'number'){
return new ReactDOMTextComponent(node);
}
//浏览器默认节点的情况
if(typeof node === 'object' && typeof node.type === 'string'){
//注意这里,使用了一种新的component
return new ReactDOMComponent(node);
    }
}

Here we have a if block, so that what you render is not text but browser element. We have another component to handle what it should return. here we see the goodie of 
instantiateReactComponent, no matter what node, we can produce a component responsible for rendering, so we don’t modify render, we just add a type of component.

so we have

//component类,用来表示文本在渲染,更新,删除时应该做些什么事情
function ReactDOMComponent(element){
//存下当前的element对象引用
this._currentElement = element;
this._rootNodeID = null;
}
//component渲染时生成的dom结构
ReactDOMComponent.prototype.mountComponent = function(rootID){
//赋值标识
this._rootNodeID = rootID;
var props = this._currentElement.props;
var tagOpen = '<' + this._currentElement.type;
var tagClose = '</' + this._currentElement.type + '>';
    //加上reactid标识
tagOpen += ' data-reactid=' + this._rootNodeID;
    //拼凑出属性
for (var propKey in props) {
        //这里要做一下事件的监听,就是从属性props里面解析拿出on开头的事件属性的对应事件监听
if (/^on[A-Za-z]/.test(propKey)) {
var eventType = propKey.replace('on', '');
//针对当前的节点添加事件代理,以_rootNodeID为命名空间
$(document).delegate('[data-reactid="' + this._rootNodeID + '"]', eventType + '.' + this._rootNodeID, props[propKey]);
}
        //对于children属性以及事件监听的属性不需要进行字符串拼接
//事件会代理到全局。这边不能拼到dom上不然会产生原生的事件监听
if (props[propKey] && propKey != 'children' && !/^on[A-Za-z]/.test(propKey)) {
tagOpen += ' ' + propKey + '=' + props[propKey];
}
}
//获取子节点渲染出的内容
var content = '';
var children = props.children || [];
    var childrenInstances = []; //用于保存所有的子节点的componet实例,以后会用到
var that = this;
$.each(children, function(key, child) {
//这里再次调用了instantiateReactComponent实例化子节点component类,拼接好返回
var childComponentInstance = instantiateReactComponent(child);
childComponentInstance._mountIndex = key;
        childrenInstances.push(childComponentInstance);
//子节点的rootId是父节点的rootId加上新的key也就是顺序的值拼成的新值
var curRootId = that._rootNodeID + '.' + key;
//得到子节点的渲染内容
var childMarkup = childComponentInstance.mountComponent(curRootId);
//拼接在一起
content += ' ' + childMarkup;
    })
    //留给以后更新时用的这边先不用管
this._renderedChildren = childrenInstances;
    //拼出整个html内容
return tagOpen + '>' + content + tagClose;
}

Here we have the definition of v-dom reactElement, and we have a whole new component class Component ReactDOMComponent, so we can implement the basic rendering of browser element.

As for the rendering logic of v-dom, essentially it’s a recursive rendering, reactElement will recursively render its child node. we can see we use instantiateReactComponent to abstract the difference of the child node difference. We just need to use differenct component class, so we can make sure we get the final rendering content by mountComponent.

Another thing to mention here, is that we can pass {onClick: function(){}} things like this, so that it will add handler to the current element, delegate it to the document, it is because that all of reactjs is writing just so that the listening function passing is relatively simple.

A lot of thing we didn’t consider here, is some specific input select, and img don’t need tagClose, here we don’t extend it for simple. And reactjs’s event handling system is complicated, it implements a w3c standard event system, here we just use jquery for ease.

Custom element

As the growing of front-end tech, the basic element can’t meet our expectation, so if you have some understanding of webcomponents, you know people are always extending the markup.

reactjs achieve similar effect by v-dom, remember that element.type is just a simple string, what if it’s a class? if the class have its own lifecycle management, then the extentibility is high.

Here’s how you use react custom element

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"));
/**
结果为:
html:
<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>
console:
我就要开始渲染了。。。
我已经渲染好了。。。
*/

Here’s how we implement React.createClass:

//定义ReactClass类,所有自定义的超级父类
var ReactClass = function(){
}
//留给子类去继承覆盖
ReactClass.prototype.render = function(){}
React = {
nextReactRootIndex:0,
createClass:function(spec){
//生成一个子类
var Constructor = function (props) {
this.props = props;
this.state = this.getInitialState ? this.getInitialState() : null;
}
//原型继承,继承超级父类
Constructor.prototype = new ReactClass();
Constructor.prototype.constructor = Constructor;
//混入spec到原型
$.extend(Constructor.prototype,spec);
return Constructor;
    },
createElement:function(type,config,children){
...
},
render:function(element,container){
...
}
}

You can see we use createClass to get a subclass inherenting from ReactClass, and in its constructor function we call this.getInitialState to
get the initial state.

for the convenience of demonstration, our ReactClass is pretty simple, actually the original code handles a lot of situation like the mixin support,
like the way componentDidMount can be define more times, so we need to squash them up, just read the fucking code, it’s not the purpose of the blog.

Here we return the definition of inherenting a class, so the concrete componentWillMount, where should we call these lifecyle events?

Here We can see from the above two types, we sometimes need custom element and offer a component class, in that class we will instantiate the ReactClass and manage the lifecycle, and parent & child component dependence.

Let’s doo this!

function instantiateReactComponent(node){
//文本节点的情况
if(typeof node === 'string' || typeof node === 'number'){
return new ReactDOMTextComponent(node);
}
//浏览器默认节点的情况
if(typeof node === 'object' && typeof node.type === 'string'){
//注意这里,使用了一种新的component
return new ReactDOMComponent(node);
    }
//自定义的元素节点
if(typeof node === 'object' && typeof node.type === 'function'){
//注意这里,使用新的component,专门针对自定义元素
return new ReactCompositeComponent(node);
    }
}

simple enough, we just add a judgement, with the new component to handle the custom node, let’s see how to implement it

function ReactCompositeComponent(element){
//存放元素element对象
this._currentElement = element;
//存放唯一标识
this._rootNodeID = null;
//存放对应的ReactClass的实例
this._instance = null;
}
//用于返回当前自定义元素渲染时应该返回的内容
ReactCompositeComponent.prototype.mountComponent = function(rootID){
this._rootNodeID = rootID;
//拿到当前元素对应的属性值
var publicProps = this._currentElement.props;
//拿到对应的ReactClass
var ReactClass = this._currentElement.type;
// Initialize the public class
var inst = new ReactClass(publicProps);
this._instance = inst;
//保留对当前comonent的引用,下面更新会用到
inst._reactInternalInstance = this;
    if (inst.componentWillMount) {
inst.componentWillMount();
//这里在原始的reactjs其实还有一层处理,就是 componentWillMount调用setstate,不会触发rerender而是自动提前合并,这里为了保持简单,就略去了
}
//调用ReactClass的实例的render方法,返回一个element或者一个文本节点
var renderedElement = this._instance.render();
//得到renderedElement对应的component类实例
var renderedComponentInstance = instantiateReactComponent(renderedElement);
this._renderedComponent = renderedComponentInstance; //存起来留作后用
    //拿到渲染之后的字符串内容,将当前的_rootNodeID传给render出的节点
var renderedMarkup = renderedComponentInstance.mountComponent(this._rootNodeID);
    //之前我们在React.render方法最后触发了mountReady事件,所以这里可以监听,在渲染完成后会触发。
$(document).on('mountReady', function() {
//调用inst.componentDidMount
inst.componentDidMount && inst.componentDidMount();
});
    return renderedMarkup;
}

It’s not that hard, ReactClass’s render is to return a virtual dom(including element & text), here we use instantiateReactComponent to get the instance then use mountComponent to get the result of the custom element by the result.

We should say the custom element is not more to handle the specific content, it handles much of the lifecycle. The specific content is determined by the render method, it returns the virtural node for rendering.

Essentially it’s also recursively get the rendering content, simulteniously because of the feature of recursion, the componentWillMount of parentComponent must be called before its children’s, and the componentDidMount must be called after the child component, because the child component must be first to be listened.

Notice that the custom element won’t handle what we passed when we createElement, it’s only going to handle its content that returned from its render. But we can use this.props.children to get the passed child nodes to handle in its render, kind of similar to shadow dom in webcomponents.

Here we have three types of element, actually there are not big difference, both have the correspoinding class to handle its rendering process.

Then we find that the initial rendering process is done.

Close word

The whle rendering process is done. Let’s see how to implement reactjs’ greate updating system, to see the core diff algorithm.

Implement updating system

Let’s see how we update.

In the reactjs when we want to update, we call setstate, see:

var HelloMessage = React.createClass({
getInitialState: function() {
return {type: 'say:'};
},
changeType:function(){
this.setState({type:'shout:'})
},
render: function() {
return React.createElement("div", {onclick:this.changeType},this.state.type, "Hello ", this.props.name);
}
});

React.render(React.createElement(HelloMessage, {name: "John"}), document.getElementById("container"));
/**
//生成的html为:
<div data-reactid="0" id="test">
<span data-reactid="0.0">hello world</span>
</div>
点击文字,say会变成shout
*/

Let’s extend ReactClass, see how to have setState:

//定义ReactClass类
var ReactClass = function(){
}
ReactClass.prototype.render = function(){}
//setState
ReactClass.prototype.setState = function(newState) {
    //还记得我们在ReactCompositeComponent里面mount的时候 做了赋值
//所以这里可以拿到 对应的ReactCompositeComponent的实例_reactInternalInstance
this._reactInternalInstance.receiveComponent(null, newState);
}

As you can see setState call the corresponding receiveComponent of the component to have update. All of the mounting, updating should be
managed by the corresponding component.

As all the component have implemented the mountComponent to handle the first rendering, every component need to have receiveComponent to handle
its own updating.

receiveComponent of the custeom element

So we have implemented receiveComponent of the corresponding component,

//更新
ReactCompositeComponent.prototype.receiveComponent = function(nextElement, newState) {
    //如果接受了新的,就使用最新的element
this._currentElement = nextElement || this._currentElement
    var inst = this._instance;
//合并state
var nextState = $.extend(inst.state, newState);
var nextProps = this._currentElement.props;

    //改写state
inst.state = nextState;

    //如果inst有shouldComponentUpdate并且返回false。说明组件本身判断不要更新,就直接返回。
if (inst.shouldComponentUpdate && (inst.shouldComponentUpdate(nextProps, nextState) === false)) return;
    //生命周期管理,如果有componentWillUpdate,就调用,表示开始要更新了。
if (inst.componentWillUpdate) inst.componentWillUpdate(nextProps, nextState);

    var prevComponentInstance = this._renderedComponent;
var prevRenderedElement = prevComponentInstance._currentElement;
//重新执行render拿到对应的新element;
var nextRenderedElement = this._instance.render();

    //判断是需要更新还是直接就重新渲染
//注意这里的_shouldUpdateReactComponent跟上面的不同哦 这个是全局的方法
if (_shouldUpdateReactComponent(prevRenderedElement, nextRenderedElement)) {
//如果需要更新,就继续调用子节点的receiveComponent的方法,传入新的element更新子节点。
prevComponentInstance.receiveComponent(nextRenderedElement);
//调用componentDidUpdate表示更新完成了
inst.componentDidUpdate && inst.componentDidUpdate();
    } else {
//如果发现完全是不同的两种element,那就干脆重新渲染了
var thisID = this._rootNodeID;
//重新new一个对应的component,
this._renderedComponent = this._instantiateReactComponent(nextRenderedElement);
//重新生成对应的元素内容
var nextMarkup = _renderedComponent.mountComponent(thisID);
//替换整个节点
$('[data-reactid="' + this._rootNodeID + '"]').replaceWith(nextMarkup);
    }
}
//用来判定两个element需不需要更新
//这里的key是我们createElement的时候可以选择性的传入的。用来标识这个element,当发现key不同时,我们就可以直接重新渲染,不需要去更新了。
var _shouldUpdateReactComponent = function(prevElement, nextElement){
if (prevElement != null && nextElement != null) {
var prevType = typeof prevElement;
var nextType = typeof nextElement;
if (prevType === 'string' || prevType === 'number') {
return nextType === 'string' || nextType === 'number';
} else {
return nextType === 'object' && prevElement.type === nextElement.type && prevElement.key === nextElement.key;
}
}
return false;
}

Don’t be shocked by the code, it’s actually simple. It merge the changes, get the new state, props, then compare the old element
that it render with the new render element(_shouldUpdateReactComponent), to see if it needs updating, if it does, then if you
wanna update, you just call the corresponding receiveComponent of the component class, it’s actually like to be a boss, and dispatch
job to your employer. And there are another situation, that is, the two element differs so much, it’s not the same type, which is simple,
because we just need to get the new piece code to re-render. Okay-dokay!

Essentially, it’s still receiveComponent’s recursion.

Notice two function here:

  • inst.shouldComponentUpdate is instance method, if we don’t want some setState to update the UI, we just override the method.
  • _shouldUpdateReactComponent is a globle method, it’s a reactjs optimising mechanism. Used for judging if you need to use the whole content
    for replacement, or there are just small details, if the key of the output of the two render call is differenct, we just need to rerender and
    replace. Otherwise we need to have a recursive update, to make sure of the minimum update mechanism, so that there won’t be a big splash.

Another thing we have here is a set of lifecycle mechanism.

textNode’s receiveComponent

ReactDOMTextComponent.prototype.receiveComponent = function(nextText) {
var nextStringText = '' + nextText;
//跟以前保存的字符串比较
if (nextStringText !== this._currentElement) {
this._currentElement = nextStringText;
//替换整个节点
$('[data-reactid="' + this._rootNodeID + '"]').html(this._currentElement);
    }
}

basic element’s receiveComponent

see the html

<div id="test" name="hello">
<span></span>
<span></span>
</div>

how do we update the dom with the minimum effort?

  1. property update, including special property like event handling
  2. child node’s update, this is kind of complecated, to get the best effort, we need to take care the following problem:
  3. get the new node tree with the old one, get the diff.
  4. get the diff and update them once again, we call it patch
ReactDOMComponent.prototype.receiveComponent = function(nextElement) {
var lastProps = this._currentElement.props;
var nextProps = nextElement.props;
    this._currentElement = nextElement;
//需要单独的更新属性
this._updateDOMProperties(lastProps, nextProps);
//再更新子节点
this._updateDOMChildren(nextElement.props.children);
}

it’s not that complecated

ReactDOMComponent.prototype._updateDOMProperties = function(lastProps, nextProps) {
var propKey;
//遍历,当一个老的属性不在新的属性集合里时,需要删除掉。
    for (propKey in lastProps) {
//新的属性里有,或者propKey是在原型上的直接跳过。这样剩下的都是不在新属性集合里的。需要删除
if (nextProps.hasOwnProperty(propKey) || !lastProps.hasOwnProperty(propKey)) {
continue;
}
//对于那种特殊的,比如这里的事件监听的属性我们需要去掉监听
if (/^on[A-Za-z]/.test(propKey)) {
var eventType = propKey.replace('on', '');
//针对当前的节点取消事件代理
$(document).undelegate('[data-reactid="' + this._rootNodeID + '"]', eventType, lastProps[propKey]);
continue;
}
        //从dom上删除不需要的属性
$('[data-reactid="' + this._rootNodeID + '"]').removeAttr(propKey)
}
    //对于新的属性,需要写到dom节点上
for (propKey in nextProps) {
//对于事件监听的属性我们需要特殊处理
if (/^on[A-Za-z]/.test(propKey)) {
var eventType = propKey.replace('on', '');
//以前如果已经有,说明有了监听,需要先去掉
lastProps[propKey] && $(document).undelegate('[data-reactid="' + this._rootNodeID + '"]', eventType, lastProps[propKey]);
//针对当前的节点添加事件代理,以_rootNodeID为命名空间
$(document).delegate('[data-reactid="' + this._rootNodeID + '"]', eventType + '.' + this._rootNodeID, nextProps[propKey]);
continue;
}
        if (propKey == 'children') continue;
        //添加新的属性,或者是更新老的同名属性
$('[data-reactid="' + this._rootNodeID + '"]').prop(propKey, nextProps[propKey])
}
}

it’s not that complicated for the property to change, it’s mainly just find the old useless property and just omit it, the new property
assigment, and notice the special event to make speicial handlement.

Let’s see the node update, which is the most complicated part.

ReactDOMComponent.prototype.receiveComponent = function(nextElement){
var lastProps = this._currentElement.props;
var nextProps = nextElement.props;
    this._currentElement = nextElement;
//需要单独的更新属性
this._updateDOMProperties(lastProps,nextProps);
//再更新子节点
this._updateDOMChildren(nextProps.children);
}
//全局的更新深度标识
var updateDepth = 0;
//全局的更新队列,所有的差异都存在这里
var diffQueue = [];
ReactDOMComponent.prototype._updateDOMChildren = function(nextChildrenElements){
updateDepth++
//_diff用来递归找出差别,组装差异对象,添加到更新队列diffQueue。
this._diff(diffQueue,nextChildrenElements);
updateDepth--
if(updateDepth == 0){
//在需要的时候调用patch,执行具体的dom操作
this._patch(diffQueue);
diffQueue = [];
}
}

Like we just said, updating is consist of two parts, one is recursive diffing, add the
diffing to the queue. Then apply diffing using patch on the dom at a good timing?

So what is the good timing? And wtf is updateDepth?

Notice here, _diffinternally will recursively call the receiveComponent of the child node, So if some child node is a normal browser node, it will go through _updateDOMChildren. So here we have updateDepth to record the process of recursion. Only when the updateDepth is 0 after the recursion, it show the entire diffing is over, then we can handle the diffing queue using dispatch.

So the key point is _diff and _patch.

//差异更新的几种类型
var UPATE_TYPES = {
MOVE_EXISTING: 1,
REMOVE_NODE: 2,
INSERT_MARKUP: 3
}

//普通的children是一个数组,此方法把它转换成一个map,key就是element的key,如果是text节点或者element创建时并没有传入key,就直接用在数组里的index标识
function flattenChildren(componentChildren) {
var child;
var name;
var childrenMap = {};
for (var i = 0; i < componentChildren.length; i++) {
child = componentChildren[i];
name = child && child._currentelement && child._currentelement.key ? child._currentelement.key : i.toString(36);
childrenMap[name] = child;
}
return childrenMap;
}

//主要用来生成子节点elements的component集合
//这边注意,有个判断逻辑,如果发现是更新,就会继续使用以前的componentInstance,调用对应的receiveComponent。
//如果是新的节点,就会重新生成一个新的componentInstance,
function generateComponentChildren(prevChildren, nextChildrenElements) {
var nextChildren = {};
nextChildrenElements = nextChildrenElements || [];
$.each(nextChildrenElements, function(index, element) {
var name = element.key ? element.key : index;
var prevChild = prevChildren && prevChildren[name];
var prevElement = prevChild && prevChild._currentElement;
var nextElement = element;
        //调用_shouldUpdateReactComponent判断是否是更新
if (_shouldUpdateReactComponent(prevElement, nextElement)) {
//更新的话直接递归调用子节点的receiveComponent就好了
prevChild.receiveComponent(nextElement);
//然后继续使用老的component
nextChildren[name] = prevChild;
} else {
//对于没有老的,那就重新新增一个,重新生成一个component
var nextChildInstance = instantiateReactComponent(nextElement, null);
//使用新的component
nextChildren[name] = nextChildInstance;
}
})
    return nextChildren;
}
//_diff用来递归找出差别,组装差异对象,添加到更新队列diffQueue。
ReactDOMComponent.prototype._diff = function(diffQueue, nextChildrenElements) {
var self = this;
//拿到之前的子节点的 component类型对象的集合,这个是在刚开始渲染时赋值的,记不得的可以翻上面
//_renderedChildren 本来是数组,我们搞成map
var prevChildren = flattenChildren(self._renderedChildren);
//生成新的子节点的component对象集合,这里注意,会复用老的component对象
var nextChildren = generateComponentChildren(prevChildren, nextChildrenElements);
//重新赋值_renderedChildren,使用最新的。
self._renderedChildren = []
$.each(nextChildren, function(key, instance) {
self._renderedChildren.push(instance);
})

  var nextIndex = 0; //代表到达的新的节点的index
//通过对比两个集合的差异,组装差异节点添加到队列中
for (name in nextChildren) {
if (!nextChildren.hasOwnProperty(name)) {
continue;
}
var prevChild = prevChildren && prevChildren[name];
var nextChild = nextChildren[name];
//相同的话,说明是使用的同一个component,所以我们需要做移动的操作
if (prevChild === nextChild) {
//添加差异对象,类型:MOVE_EXISTING
diffQueue.push({
parentId: self._rootNodeID,
parentNode: $('[data-reactid=' + self._rootNodeID + ']'),
type: UPATE_TYPES.MOVE_EXISTING,
fromIndex: prevChild._mountIndex,
toIndex: nextIndex
})
} else { //如果不相同,说明是新增加的节点
//但是如果老的还存在,就是element不同,但是component一样。我们需要把它对应的老的element删除。
if (prevChild) {
//添加差异对象,类型:REMOVE_NODE
diffQueue.push({
parentId: self._rootNodeID,
parentNode: $('[data-reactid=' + self._rootNodeID + ']'),
type: UPATE_TYPES.REMOVE_NODE,
fromIndex: prevChild._mountIndex,
toIndex: null
})
        //如果以前已经渲染过了,记得先去掉以前所有的事件监听,通过命名空间全部清空
if (prevChild._rootNodeID) {
$(document).undelegate('.' + prevChild._rootNodeID);
}
      }
//新增加的节点,也组装差异对象放到队列里
//添加差异对象,类型:INSERT_MARKUP
diffQueue.push({
parentId: self._rootNodeID,
parentNode: $('[data-reactid=' + self._rootNodeID + ']'),
type: UPATE_TYPES.INSERT_MARKUP,
fromIndex: null,
toIndex: nextIndex,
markup: nextChild.mountComponent() //新增的节点,多一个此属性,表示新节点的dom内容
})
}
//更新mount的index
nextChild._mountIndex = nextIndex;
nextIndex++;
}
  //对于老的节点里有,新的节点里没有的那些,也全都删除掉
for (name in prevChildren) {
if (prevChildren.hasOwnProperty(name) && !(nextChildren && nextChildren.hasOwnProperty(name))) {
//添加差异对象,类型:REMOVE_NODE
diffQueue.push({
parentId: self._rootNodeID,
parentNode: $('[data-reactid=' + self._rootNodeID + ']'),
type: UPATE_TYPES.REMOVE_NODE,
fromIndex: prevChild._mountIndex,
toIndex: null
})
//如果以前已经渲染过了,记得先去掉以前所有的事件监听
if (prevChildren[name]._rootNodeID) {
$(document).undelegate('.' + prevChildren[name]._rootNodeID);
}
}
}
}

Oh, wtf there are a whole lot shit of code, so complicated, no worry.

First we get the a collection of previous component, if it’s for the first time to update, the value is what we assigns when we render. Then we call generateComponentChildren to get the new collection of component. We know that the component is used for putting element, 一个萝卜一个坑。

Notice here flattenChildren we make the collection to a object map, anke make the key as the element’s key, certainly, as for text or an element without key, we just use index as the mark. With the marks, we can see if the two component is the same from the type perspective.

generateComponentChildren will reuse previous component as much as possible, that is the 坑. When we find the reusable component(which is the key is the same), then you use the previous one, you only need to call its updating method receiveComponent. Then you can recursively get the child node diffing object and put in the queue. If you find you can’t reuse it then it’s new node, we need to use instantiateReactComponent to generate a new component.

Here we give a big attention to flattenChildren, like as for a form list, we insert a piece of data in the front. Think that if we don’t create element with the key, thhen all the key would be null, so that when you generateComponentChildren you will by default change the previous and latter child node by the index. So judging(_shouldUpdateReactComponent) before and after changing the corresponding node is not appropriate, that is, we had better to give it key, to find the relationship.

When we have the new comopnent, we need to diff, make up the diff object.

Comparing the old and new, we need to cover four situations, including three kind of update:

MOVE_EXISTING: the new component exsist in our old collection, and element is a type which can be updated, and when we generateComponentChildren we have called receiveComponent, in this situation we have prevChild = nextChild, then we need to do movation, to reuse the previous dom node.

INSERT_MARKUP: the new componenent is not in the old collection, then it’s mint in box.

REMOVE_NODE: old component exist in the new collection, but the corresponding element is not the same, so we can’t reuse it so we need to delete it.

REMOVE_NODE: old component don’t exist in the new collection, we need to delete

So we have these three type of diff, make them up of a diff object, and add to the diffing queue.

For example, let’s see the example below, let’s assume that these are some child element of the parent element’s collection, then the above to the below is process of the changing

[1] [2] [3]
||
| |
[4] [2] [1]

we can treat the number as the key of the element.

From above down, we can see the 2 of the 4, 2, 1 can reuse the previous component, and let them know that their child nodes are updated, then tell 2 and 1, where they need to move in the new collection(here, it’s composing up the diffing object and add to the queue). 3 needs deletion, 4 needs increment.

OK, here’s the diffing process, when the diffing is done, we need to do patching, to make these diff object to have an impact on the real dom.

Let’s see the implementation of _patch:

//用于将childNode插入到指定位置
function insertChildAt(parentNode, childNode, index) {
var beforeChild = parentNode.children().get(index);
beforeChild ? childNode.insertBefore(beforeChild) : childNode.appendTo(parentNode);
}
ReactDOMComponent.prototype._patch = function(updates) {
var update;
var initialChildren = {};
var deleteChildren = [];
for (var i = 0; i < updates.length; i++) {
update = updates[i];
if (update.type === UPATE_TYPES.MOVE_EXISTING || update.type === UPATE_TYPES.REMOVE_NODE) {
var updatedIndex = update.fromIndex;
var updatedChild = $(update.parentNode.children().get(updatedIndex));
var parentID = update.parentID;
            //所有需要更新的节点都保存下来,方便后面使用
initialChildren[parentID] = initialChildren[parentID] || [];
//使用parentID作为简易命名空间
initialChildren[parentID][updatedIndex] = updatedChild;

            //所有需要修改的节点先删除,对于move的,后面再重新插入到正确的位置即可
deleteChildren.push(updatedChild)
}
    }
    //删除所有需要先删除的
$.each(deleteChildren, function(index, child) {
$(child).remove();
})

    //再遍历一次,这次处理新增的节点,还有修改的节点这里也要重新插入
for (var k = 0; k < updates.length; k++) {
update = updates[k];
switch (update.type) {
case UPATE_TYPES.INSERT_MARKUP:
insertChildAt(update.parentNode, $(update.markup), update.toIndex);
break;
case UPATE_TYPES.MOVE_EXISTING:
insertChildAt(update.parentNode, initialChildren[update.parentID][update.fromIndex], update.toIndex);
break;
case UPATE_TYPES.REMOVE_NODE:
// 什么都不需要做,因为上面已经帮忙删除掉了
break;
}
}
}

what the _path do is it go through the queue for twice, first it remove all the nodes need updating, then it insert all the new node and the changing node. Why can we directly inserting one by one? It is because that when we are diffing and adding the diffing node to the diffing queue, the whole process is in order, that is, for all the new node(including move & insert), in the queue, the order is the final order of dom, so that we can add the nodes by the index one by one.

But actually you will find here’s a problem, that is all the node will be deleted including reused component whi is UPATE_TYPES.MOVE_EXISTING, so the splashing would be serious. Actually let’s see the previous example, node 2 don’t need to be recorded to the diffing queue, and the next patch is ok. Think why.

Let’s improve the code.

//_diff用来递归找出差别,组装差异对象,添加到更新队列diffQueue。
ReactDOMComponent.prototype._diff = function(diffQueue, nextChildrenElements){
。。。
/**注意新增代码**/
var lastIndex = 0;//代表访问的最后一次的老的集合的位置
var nextIndex = 0;//代表到达的新的节点的index
//通过对比两个集合的差异,组装差异节点添加到队列中
for (name in nextChildren) {
if (!nextChildren.hasOwnProperty(name)) {
continue;
}
var prevChild = prevChildren && prevChildren[name];
var nextChild = nextChildren[name];
//相同的话,说明是使用的同一个component,所以我们需要做移动的操作
if (prevChild === nextChild) {
//添加差异对象,类型:MOVE_EXISTING
。。。。
/**注意新增代码**/
prevChild._mountIndex < lastIndex && diffQueue.push({
parentId:this._rootNodeID,
parentNode:$('[data-reactid='+this._rootNodeID+']'),
type: UPATE_TYPES.REMOVE_NODE,
fromIndex: prevChild._mountIndex,
toIndex:null
})
lastIndex = Math.max(prevChild._mountIndex, lastIndex);
} else {
//如果不相同,说明是新增加的节点,
if (prevChild) {
//但是如果老的还存在,就是element不同,但是component一样。我们需要把它对应的老的element删除。
//添加差异对象,类型:REMOVE_NODE
。。。。。
/**注意新增代码**/
lastIndex = Math.max(prevChild._mountIndex, lastIndex);
}
。。。
}
//更新mount的inddex
nextChild._mountIndex = nextIndex;
nextIndex++;
}
      //对于老的节点里有,新的节点里没有的那些,也全都删除掉
。。。
}

Notice here we have lastIndex, which represent the last visit positio of the old collection nodes. So we add a judgement, only _mountIndex which is less than the lastIndex need to be added to the diffing queue. With this, the above example node 2 don’t need to move, and the program will run well, actually most situation is like 2.

This is an order optimisization, lastIndex is updating, representing the current visiting eldest one of the collection. Let’s assume the last element is A, we update the lastIndex after adding it. If here we have new element B, if it’s greater than the lastIndex, it shows that the current element in the oldCollection is after A, so the element won’t need to be add to the diffing queue, and it won’t affect others. It won’t inclunce the node after the patching node. Because we can know from the patching, the

Here we have the whole updating mechanism. Let’s review the diffing algorithm.

First each component implements receiveComponent to handle its own updating. And the browser default element updating is the most complicated, which is the diff algorithm.

React have a globle _shouldUpdateReactComponent to judge if it needs updating or rerender by the key of the element. This is the first diffing judgement. Like in the custom element, we use the judgement, this is going to be super effective.

Every type of element needs the handle its own updating:

  1. custom element updating, mainly updating the rendered node, and handle the node it renders to the corresponding componenent.
  2. updating of text node, super easy, just the text.
  3. browser element updating, two part.

3.1 property updating, comparing the difference of the two property, partial updates. And handle the special property, like the binding of the event.

3.2 next is the updating of the child node. To update the child node is to find the diffing object, at the same time, we use _shouldUpdateReactComponent to judge, if it can update, then recursively update the child node. Then we recursively find the diffing object. Here we use the optimisization technique “lastIndex”, to make some node keep its position, then manipulate the dom element by the diffing object(move, delete, add)

That’s all.

Try this shit out

Let’s make a simple todolist

var TodoList = React.createClass({
getInitialState: function() {
return {items: []};
},
add:function(){
var nextItems = this.state.items.concat([this.state.text]);
this.setState({items: nextItems, text: ''});
},
onChange: function(e) {
this.setState({text: e.target.value});
},
render: function() {
var createItem = function(itemText) {
return React.createElement("div", null, itemText);
};
    var lists = this.state.items.map(createItem);
var input = React.createElement("input", {onkeyup: this.onChange.bind(this),value: this.state.text});
var button = React.createElement("p", {onclick: this.add.bind(this)}, 'Add#' + (this.state.items.length + 1))
var children = lists.concat([input,button])
    return React.createElement("div", null,children);
}
});

React.render(React.createElement(TodoList), document.getElementById("container"));

The process is like this:

  • When first rendering we use ReactCompositeComponent to render the custom element TodoList, get the initial value by the getInitialState, then we use ReactDOMComponent to render the div bsic element node rendered, the div element then continuesly use ReactDOMComponent to render each element, including child element, input and p.
  • the onchange event triggered in the input, then we call setstate to make changes, then we update the node rendered, after the diffing algorithm, we go deep and go deep. Finally we change the value.
  • click the button, we trigger the update, after the updating alogthrim and add a node. simulteniously we update the text of the button.

Mission accomplished.

(translator: I love you, gosh you should learn some Chinese)

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.