摘要
在上一篇中,實(shí)現(xiàn)了多節(jié)點(diǎn)的渲染。但是之前寫得diff算法,只能適用于單節(jié)點(diǎn)的情況,例如這種情況:
<div>
<p>
<span></span>
</p>
</div>
如果對(duì)于多節(jié)點(diǎn)的情況:
<ul>
<li></li>
<li></li>
<li></li>
</ul>
之前實(shí)現(xiàn)的diff算法就不會(huì)有效果了,所以在這一篇中,我們主要實(shí)現(xiàn)針對(duì)于多節(jié)點(diǎn)的diff算法。
實(shí)現(xiàn)之前,我們先將index.js修改一下:
function App() {
const [num, setNum] = useState(100)
const click1 = () => {
console.log(num);
setNum(num + 1)
}
return num % 2 > 0 ? jsx("ul", {
onClick: click1,
key: 'ul',
children: [jsx("li", {
children: "1",
key: "1"
}), jsx("li", {
children: "2",
key: "2"
}), jsx("li", {
children: "3",
key: "3"
})]
}): jsx("ul", {
onClick: click1,
key: 'ul',
children: [jsx("li", {
children: "2",
key: "2"
}), jsx("li", {
children: "1",
key: "1"
}), jsx("li", {
children: "3",
key: "3"
})]
});
}
ReactDOM.createRoot(root).render(<App />)
1.修改beginWork流程
在reconcileChildren方法里面,我們判斷了如果element為數(shù)組的情況,就是多節(jié)點(diǎn)。所以我們需要在這里進(jìn)行diff算法的處理。
function reconcileChildren(parent,element) {
//其他代碼。。。。
}else if(Array.isArray(element) && element.length > 0) {
const newChild = diffReconcileManyChildren(parent, element);
if(newChild) {
return newChild
}
//其他代碼。。。。
所以我們的diff算法那主要是在diffReconcileManyChildren方法里面實(shí)現(xiàn)。
對(duì)于多節(jié)點(diǎn)的Diff,我們需要進(jìn)行以下步驟。
- 創(chuàng)建變量lastIndex,用來(lái)標(biāo)記索引
- 將舊的filberNode列表,轉(zhuǎn)換為map結(jié)構(gòu),key為filberNode的key,value為filberNode
- 遍歷新的element數(shù)組。
- 如果element.key可以在map中找到,lastIndex記錄為找到的filberNode的index
- 如果找不到,創(chuàng)建新的FilberNode
- 繼續(xù)遍歷,如果又在map中找到filberNode,比較fiberNode的index和lastIndex.
- 如果index < lastIndex,給filberNode打上移動(dòng)的標(biāo)志
基于上面的步驟,實(shí)現(xiàn)diffReconcileManyChildren方法
function diffReconcileManyChildren(filberNode, element) {
let firstChild = filberNode.child;
if(!firstChild) {
return;
}
const head = {
sibling: null
};
const oldChildren = []
while(firstChild) {
oldChildren.push(firstChild);
firstChild = firstChild.sibling;
}
const oldMap = new Map();
oldChildren.forEach((item,index) => {
item.index = index
if(item.key) {
oldMap.set(item.key, item)
}else{
oldMap.set(index, item)
}
})
let lastIndex = 0;
let empty = head
for(let i=0; i<element.length; i++) {
if(!element[i].key){
continue;
}
const useFilber = oldMap.get(element[i].key);
useFilber.sibling = null;
if(useFilber) {
if(useFilber.index < lastIndex) {
useFilber.flags = 'insert'
}
useFilber.memoizedProps = element[i]
lastIndex = useFilber.index;
empty.sibling = useFilber;
empty = empty.sibling;
oldMap.delete(element[i].key)
}else{
const filberNode = new FilberNode(HostComponent, element[i].props, element[i].key)
filberNode.type = element[i].type
empty.sibling = filberNode;
empty = empty.sibling;
}
}
return head.sibling;
}
經(jīng)過(guò)上面的處理,beginWork流程結(jié)束,可復(fù)用的filberNode就不會(huì)重復(fù)創(chuàng)建。
2.修改completeWork流程
在beginWork中,可復(fù)用的節(jié)點(diǎn)已經(jīng)被打上了insert的標(biāo)志,所以在updateCompleteHsotComponent中,我們要判斷是不是insert的標(biāo)志,如果是,就不能無(wú)腦創(chuàng)建,而是通過(guò)移動(dòng)DOM的位置來(lái)復(fù)用DOM。
同時(shí),也要對(duì)同級(jí)的sibling進(jìn)行遞歸處理。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-661960.html
function updateCompleteHostComponent(filberNode) {
//其他代碼。。。。
if(element.key === filberNode.key && element.type === filberNode.type) {
addPropsToDOM(filberNode.stateNode, filberNode.pendingProps);
if(filberNode.flags === 'insert') {
const parent = filberNode.return;
parent.stateNode.insertBefore(filberNode.stateNode, filberNode.sibling?.stateNode)
}
//其他代碼
if(filberNode.sibling) {
completeWork(filberNode.sibling)
}
}
在對(duì)HostText的處理中,也要考慮,當(dāng)前的操作是更新還是替換。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-661960.html
function completeHostText(filberNode) {
//其他代碼。。。。。
if(parent && parent.stateNode && parent.tag === HostComponent) {
if(!parent.stateNode) {
parent.stateNode.appendChild(element);
}else{
parent.stateNode.replaceChildren(element);
}
}
//其他代碼。。。。
}
到了這里,關(guān)于React源碼解析18(10)------ 實(shí)現(xiàn)多節(jié)點(diǎn)的Diff算法的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!