作为
React
的核心技术之一Virtual DOM
,一直披着神秘的面纱。
实际上,Virtual DOM包含:
- Javascript DOM模型树(VTree),类似文档节点树(DOM)
- DOM模型树转节点树方法(VTree -> DOM)
- 两个DOM模型树的差异算法(diff(VTree, VTree) -> PatchObject)
- 根据差异操作节点方法(patch(DOMNode, PatchObject) -> DOMNode)
接下来我们分别探讨这几个部分:
VTree
VTree模型非常简单,基本结构如下:
{
// tag的名字
tagName: 'p',
// 节点包含属性
properties: {
style: {
color: '#fff'
}
},
// 子节点 children: [], // 该节点的唯一表示,后面会讲有啥用 key: 1 }
所以我们很容易写一个方法来创建这种树状结构,例如React是这么创建的:
// 创建一个div
react.createElement('div', null, [
// 子节点img
react.createElement('img', { src: "avatar.png", class: "profile" }), // 子节点h3 react.createElement('h3', null, [[user.firstName, user.lastName].join(' ')]) ]);
VTree -> DOM
这方法也不太难,我们实现一个简单的:
function create(vds, parent) {
// 首先看看是不是数组,如果不是数组统一成数组
!Array.isArray(vds) && (vds = [vds]);
// 如果没有父元素则创建个fragment来当父元素 parent = parent || document.createDocumentFragment(); var node; // 遍历所有VNode vds.forEach(function (vd) { // 如果VNode是文字节点 if (isText(vd)) { // 创建文字节点 node = document.createTextNode(vd.text); // 否则是元素 } else { // 创建元素 node = document.createElement(vd.tag); } // 将元素塞入父容器 parent.appendChild(node); // 看看有没有子VNode,有孩子则处理孩子VNode vd.children && vd.children.length && create(vd.children, node); // 看看有没有属性,有则处理属性 vd.properties && setProps({ style: {} }, vd.properties, node); }); return parent; }
diff(VTree, VTree) -> PatchObject
差异算法是Virtual DOM的核心,实际上该差异算法是个取巧算法(当然你不能指望用O(n^3)的复杂度来解决两个树的差异问题吧),不过能解决Web的大部分问题。
那么React是如何取巧的呢?
- 分层对比
如图,React仅仅对同一层的节点尝试匹配,因为实际上,Web中不太可能把一个Component在不同层中移动。
- 基于key来匹配
还记得之前在VTree中的属性有一个叫key的东东么?这个是一个VNode的唯一识别,用于对两个不同的VTree中的VNode做匹配的。
这也很好理解,因为我们经常会在Web遇到拥有唯一识别的Component(例如课程卡片、用户卡片等等)的不同排列问题。
- 基于自定义元素做优化
React提供自定义元素,所以匹配更加简单。
patch(DOMNode, PatchObject) -> DOMNode
由于diff操作已经找出两个VTree不同的地方,只要根据计算出来的结果,我们就可以对DOM的进行差异渲染。
扩展阅读
具体可参考下面两份代码实现:
- @Matt-Esch实现的:virtual-dom
- 我们自己做的简版实现,用于Mobile页面渲染的:qvd