一、什麼是Reactdiff
Reactdiff是React框架中一個用於虛擬DOM比較的算法。它的作用是在虛擬DOM中找出改變的節點,最小化DOM操作,提高性能。
二、Reactdiff的基本原理
Reactdiff算法的基本原理是將舊樹和新樹遞歸地進行對比,找出二者之間的差別,並將其記錄下來。它實際上是在比較兩個樹的節點
節點比較有3種情況:
1、節點類型不同,直接替換
2、節點類型相同,但節點的key不同,直接替換
3、節點類型相同且節點的key相同,更新節點的props
在比較節點時,React只會比較同一級節點,不會跨級比較,因為跨級比較非常消耗性能。
同時,當發現一個節點需要更新時,React只會更新這個節點及其子節點,而不是全量更新整個DOM樹。
三、Reactdiff的優化策略
1、Key的作用
在Reactdiff中,key用於標記列表項,可以幫助React識別哪些列表項發生了變化,避免不必要的DOM操作。在更新列表時,React根據key判斷新節點和舊節點是否匹配,從而判斷節點是否需要更新。
2、批量更新
React支持批量更新。它會將多個setState的操作合併成一次DOM更新。這樣可以最小化DOM操作,提高性能。
handleClick = () => {
this.setState({ name: 'Lucy' });
this.setState({ age: 18 });
}
上面的代碼中,React會將兩個setState合併,只進行一次DOM操作。
3、桶排序
當列表項順序會發生變化時,Reactdiff會使用桶排序算法。它會將新列表中的節點放到一個桶里,再遍歷舊列表中的節點,找到對應的新節點,如果沒有則刪除舊節點。
const oldList = ['a', 'b', 'c'];
const newList = ['c', 'b', 'a'];
const map = new Map();
const result = [];
newList.forEach((item, index) => {
map.set(item, index);
});
oldList.forEach((item) => {
const index = map.get(item);
if (index !== undefined) {
result.push(item);
map.delete(item);
}
});
newList.forEach((item) => {
if (map.has(item)) {
result.push(item);
}
});
console.log(result); // ['a', 'b', 'c']
上面的代碼中,我們使用了桶排序算法,將新列表中的節點放到一個桶里,再遍歷舊列表中的節點,找到對應的新節點,如果沒有則刪除舊節點。
四、Reactdiff的代碼實現
1、節點比較
function diffNode(oldNode, newNode) {
// 節點類型不同,直接替換
if (oldNode.type !== newNode.type) {
return newNode;
}
// 更新節點屬性
updateNode(oldNode, newNode);
// 返回舊節點
return oldNode;
}
2、更新節點屬性
function updateNode(oldNode, newNode) {
const oldProps = oldNode.props;
const newProps = newNode.props;
// 更新屬性
Object.keys(newProps)
.filter(propName => oldProps[propName] !== newProps[propName])
.forEach(propName => {
oldNode.dom[propName] = newProps[propName];
});
// 刪除屬性
Object.keys(oldProps)
.filter(propName => !(propName in newProps))
.forEach(propName => {
oldNode.dom.removeAttribute(propName);
});
// 添加屬性
Object.keys(newProps)
.filter(propName => !(propName in oldProps))
.forEach(propName => {
oldNode.dom[propName] = newProps[propName];
});
}
3、虛擬DOM的比較
function diff(root, vNode) {
const patches = [];
walk(root, vNode, 0, patches);
return patches;
}
function walk(oldNode, newNode, index, patches) {
const currentPatch = [];
// 節點被刪除
if (!newNode) {
currentPatch.push({ type: REMOVE_NODE });
} else if (isString(oldNode) && isString(newNode)) {
// 文本節點更新
if (oldNode !== newNode) {
currentPatch.push({ type: TEXT, content: newNode });
}
} else if (oldNode.type === newNode.type) {
// 節點類型相同,更新子節點
const propsPatches = diffProps(oldNode.props, newNode.props);
if (propsPatches.length) {
currentPatch.push({ type: PROPS, props: propsPatches });
}
// 比較子節點
diffChildren(oldNode, newNode, currentPatch, patches);
} else {
// 節點類型不同,直接替換
currentPatch.push({ type: REPLACE_NODE, node: newNode });
}
if (currentPatch.length) {
patches[index] = currentPatch;
}
}
4、節點屬性的比較
function diffProps(oldProps, newProps) {
const patches = [];
// 更新屬性
Object.keys(newProps)
.filter(propName => oldProps[propName] !== newProps[propName])
.forEach(propName => {
patches.push({ type: SET_PROP, name: propName, value: newProps[propName] });
});
// 刪除屬性
Object.keys(oldProps)
.filter(propName => !(propName in newProps))
.forEach(propName => {
patches.push({ type: REMOVE_PROP, name: propName });
});
return patches;
}
5、更新子節點
function diffChildren(oldNode, newNode, currentPatch, patches) {
const oldChildren = oldNode.children;
const newChildren = newNode.children;
const maxLength = Math.max(oldChildren.length, newChildren.length);
for (let i = 0; i < maxLength; i++) {
const childPathces = [];
walk(oldChildren[i], newChildren[i], i, childPathces);
currentPatch.push({ type: REORDER_NODE, moves: childPathces });
}
}
五、總結
Reactdiff算法的優化,可以讓React在操作大量DOM節點時極大地提升性能,同時也可以最小化不必要的DOM操作,優化渲染。我們可以通過比較節點類型、節點屬性,以及使用Key等方法,減少不必要的DOM操作。
原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-hk/n/285133.html