虚拟DOM
1.虚拟DOM是什么?
虚拟DOM就是虚拟节点。React用JS对象来模拟真实的DOM节点,然后将其渲染成真实的DOM节点。
2.怎么实现?
- 第一步是模拟 用JSX语法写出来的div其实就是一个虚拟节点:
<div id ="test">
<span class="red">hello world</span>
</div>
这代码会得到这样一个对象:
tag: 'div',
props: {
id: 'test',
},
childred: [
{
tag: 'span',
props: {
className: 'red',
},
children: ['hello world'],
},
],
};
能做到这一点是因为JSX语法会被转义为createElement函数调用(也叫h函数),如下:
React.createElement('div', { id: 'test' },
React.createElement('span', { class: 'red' }, 'hello world'));
- 第二步是将虚拟节点渲染成真实的节点
function render(vdom) {
//如果是字符串或者数字,创建一个文本节点
if (typeof vdom === 'string' || typeof vdom === 'number') {
return document.createTextNode(vdom);
}
const { tag, props, childred } = vdom;
//创建真实DOM
const element = document.createElement(tag);
//设置属性
setProps(element, props);
//遍历子节点,并获取创建真实DOM,插入到当前节点
childred.map(render).forEach(element.appendChild.bind(element));
//虚拟DOM中缓存真实DOM节点
vdom.dom = element;
//返回DOM节点
return element;
}
function setProps() {} //略
注意,如果节点发生变化,并不会直接把新虚拟节点渲染到真实节点,而是先经过diff算法得到一个patch再更新到真实节点上。
3.解决了什么问题?
- DOM 操作性能问题,通过虚拟DOM和diff算法减少不必要的DOM操作,保证性能不太差
- DOM操作不方便问题。以前各种DOM API要记住,现在只有setState
DOM diff
1.DOM diff是什么?
DOM diff 是对比两颗虚拟DOM树的算法。当组件变化时,会render出一个新的虚拟虚拟DOM,diff算法对比新旧虚拟DOM树之后,得到一个patch,然后React用patch来更新真实DOM。
2.怎么做?
- 首先对比两棵树的根节点
-
- 如果根节点的类型改变了,比如div变成了p,那么认为整棵树都变了,不再对比子节点,直接删除对应的真实DOM树,创建新的真实DOM树。
-
- 如果根节点的类型没变,就直接看属性的变了没有,没变保留对应的真实节点,变了的话,更新该节点的属性,不重新创建节点。
- 然后同时遍历两颗树的子节点,每个节点对比过程同上
-
- 当在子节点的末尾添加一个元素时,这两个树之间的转换效果很好:
<ul>
<li>first</li>
<li>second</li>
</ul>
<ul>
<li>first</li>
<li>second</li>
<li>third</li>
</ul>
React 会匹配两<li>first</li>棵树,匹配两<li>second</li>棵树,然后插入<li>third</li>树。
-
- 在开头插入一个元素的性能会更差
<ul>
<li>Duke</li>
<li>Villanova</li>
</ul>
<ul>
<li>Connecticut</li>
<li>Duke</li>
<li>Villanova</li>
</ul>
React 会改变每个孩子,而不是意识到它可以保持<li>Duke</li>和<li>Villanova</li>子树完整。这种低效率可能是一个问题。
为了解决这个问题,React 支持一个key属性。当孩子有键时,React 使用键将原始树中的孩子与后续树中的孩子进行匹配。例如,key在我们上面的低效示例中添加 a 可以使树转换高效:
<ul>
<li key="2015">Duke</li>
<li key="2016">Villanova</li>
</ul>
<ul>
<li key="2014">Connecticut</li>
<li key="2015">Duke</li>
<li key="2016">Villanova</li>
</ul>
现在 React 知道带有 key 的元素'2014'是新元素,带有 key 和 的元素'2015'是'2016'刚刚移动的。
注: React DOM diff没用到双端交叉对比,源码里面有解释