假设我们的DOM长这个样子
<ul class="list">
<li>item1</li>
<li>item2</li>
</ul>
如果用JS Object表达上面的 dom,表达方式如下
{
type: 'ul',
props: { 'class': 'list' },
children: [{
type: 'li', props: {}, children: ['item1']
}, {
type: 'li', props: {}, children: ['item2']
}]
}
通过以下的函数,我们将虚拟DOM转换成真实的DOM
function createElement(node) {
if (typeof node === 'string') {
return document.createTextNode(node)
}
const $el = document.createElement(node.type)
node.children
.map(createElement)
.forEach($el.appendChild.bind($el))
return $el
}
现在我们考虑Diff,假设我们有两个virtual trees ----old and new, 我们考虑下面几种不一样的情况:
-
new tree里面有新的元素
实现方式如下:
function updateElement($parent, newNode, oldNode) {
if (!oldNode) {
$parent.appendChild(createElement(newNode))
}
}
-
new tree里面删除了元素
当要删除一个元素时,我们不知道真实的DOM节点,但此时我们可以知道这个节点的position,所以可以通过position拿到真是的DOM,然后删掉它
实现方式如下:
function updateElement($parent, newNode, oldNode, index = 0) {
if (!newNode) {
$parent.removeChild(
$parent.childNodes[index]
)
}
}
-
node 本身变了
首先,我们来检测change
function change(node1, node2) {
return typeof node1 !=== typeof node2 ||
typeof node1 === 'string' && node1 !=== node2 ||
node1.type !== node2.type
}
function updateElement($parent, newNode, oldNode, index) {
if (change(newNode, oldNode)) {
$parent.replaceChild(
createElement(newNode),
$parent.childNodes[index]
)
}
}
-
node本身没变,但是其children有可能变了
需要递归的比较children
function updateElement($parent, newNode, oldNode, index) {
if (newNode.type) {
const newLength = newNode.children.length
const oldLength = oldNode.children.length
for(let i = 0; i < newLength || i < oldLength; i++) {
updateElement(
$parent.childNode[index],
newNode.children[i],
oldNode.children[i],
i
)
}
}
}
updateElement 最终的实现方式如下:
function updateElement($parent, newNode, oldNode, index = 0) {
if (!oldNode) {
$parent.appendChild(
createElement(newNode)
);
} else if (!newNode) {
$parent.removeChild(
$parent.childNodes[index]
);
} else if (changed(newNode, oldNode)) {
$parent.replaceChild(
createElement(newNode),
$parent.childNodes[index]
);
} else if (newNode.type) {
const newLength = newNode.children.length;
const oldLength = oldNode.children.length;
for (let i = 0; i < newLength || i < oldLength; i++) {
updateElement(
$parent.childNodes[index],
newNode.children[i],
oldNode.children[i],
i
);
}
}
}
参考文献:
https://medium.com/@deathmood/how-to-write-your-own-virtual-dom-ee74acc13060