前言:
React基于两个假设:
1、两个相同的组件产生类似的DOM结构,不同组件产生不同DOM结构。
2、对于同一层次的一组子节点,它们可以通过唯一的id区分。
举个例子:(创建100个li)
a. 传统js优化后的写法:循环拼接100个,然后一次性触发dom操作。
var str = '';
for (var i = 0; i < 100; i++) {
str+='<li>'+i+'</li>';
};
document.getElementById('ul1').appendChild(str);
b. react通过深度优先遍历(DFS),循环创建createElement,createTextNode,搞毛呀这岂不是比上一种方法慢多了,所以才要引出我们的主角diff算法。
// 核心代码
Element.prototype.render = function(){
var el = document.createElement(this.tagName),
props = this.props,
propName,
propValue;
for(propName in props){
propValue = props[propName];
el.setAttribute(propName, propValue);
}
this.children.forEach(function(child){
var childEl = null;
if(child instanceof Element){
childEl = child.render();
}else{
childEl = document.createTextNode(child);
}
el.appendChild(childEl);
});
return el;
};
那为什么还用虚拟dom呢?
1)虚拟dom + diff提高渲染速度(其实浏览器也不傻,他会等一段时间再统一处理dom,所以虚拟dom多快,主要取决浏览器牛不牛逼,越牛逼虚拟dom+diff算法优势越小)。
2)我们终于可以解脱操作dom的时代了,我们可以直接用数据控制渲染我们的页面,开发效率大大提高。
2)现在我们可以控制页面是否渲染,比如shouldcomponentupdate(想让他渲染就渲染,多happy)。
主角diff算法
1. 介绍diff算法前,我们对比传统算法和diff算法。
传统算法:通过循环递归对节点进行依次对比。
diff算法:比较真实dom与虚拟dom,得出差异dom patch,只更新改变的部分。
2. diff原则
一:对比当前真实的DOM和虚拟DOM,在对比过程中直接更新真实DOM。
二:只对比同一层级的变化实现。
3. 实现:
有的框架会选择保存上次渲染的虚拟DOM,然后对比虚拟DOM前后的变化;我们选选择直接对比虚拟DOM和真实DOM,一边对比一边更新;
虚拟DOM的结构可以分为三种,分别表示文本、原生DOM节点以及组件。
// 原生DOM节点的vnode
{
tag: 'div',
attrs: {
className: 'container'
},
children: []
}
// 文本节点的vnode
"hello,world"
// 组件的vnode
{
tag: ComponentConstrucotr,
attrs: {
className: 'container'
},
children: []
}
对比类型:
1)对比文本节点:如果当前的DOM就是文本节点,则直接更新内容,否则就新建一个文本节点,并移除掉原来的DOM("hello p" => "hello ge p")。
2)对比元素节点:新旧节点不同就是,增新删旧(<div> => <p>);只新增;只删除。
3)对比属性节点:旧的属性删掉,新的属性添加(<img src='' id=''/> => <img src='' className=''/>)。
4)对比子节点:旧的属性删掉,新的属性添加(<img src='' id=''/> => <img src='' className=''/>)。
4.1 为啥对比子节点?子节点是一个数组,它们可能改变了顺序,或者 数量有所变化,我们很难确定要和虚拟DOM对比的是哪一个。
4.2 给节点设一个key值,重新渲染时对比key值相同的节点。新增dom.appendChild(child);删除removeNode( f );插入dom.insertBefore( child, f )
5)对比组件:
5.1 如果组件类型没有变化,则重新set props。(<AComponent props={oldProps} /> => <AComponent props={newProps} />)
5.2 如果组件类型变化,则移除掉原来组件,并渲染新的组件。(<AComponent /> => <BComponent />)