??emm會(huì)用react斥铺,咱都知道react用virtual dom的形式解耦了視圖層級(jí)和代碼的交互操作怯邪。
??既然托管了view層次的生成次屠,react必然得提供高效的視圖生成馏予,不然如果組件之間的轉(zhuǎn)換卡頓一定是所有人都受不了的掀宋。所以呢鸣个,在渲染html方面,react整了個(gè)diff算法布朦,提升了html重新渲染的速度。
??廢話(huà)不多說(shuō)這里先推薦兩篇文章昼窗,后面寫(xiě)的東西基本都是對(duì)于這兩篇文章的思路(圖片資源也是這里扒下來(lái)的= =)是趴。
??1:從零開(kāi)始實(shí)現(xiàn)一個(gè)React(三):diff算法
??2:React 源碼剖析系列 - 不可思議的 react diff
??首先咱聲明以及明確以下幾點(diǎn)。
??1:react的diff算法對(duì)于新舊兩顆dom樹(shù)只會(huì)對(duì)同一層級(jí)的節(jié)點(diǎn)進(jìn)行比較澄惊,如果類(lèi)型不同唆途,舊的節(jié)點(diǎn)將會(huì)直接刪除重建即使是有跨層級(jí)移動(dòng)的節(jié)點(diǎn)(節(jié)點(diǎn)內(nèi)容相同富雅,但是新的節(jié)點(diǎn)深度和舊節(jié)點(diǎn)深度不同),也是直接刪了重建肛搬,而不會(huì)進(jìn)行節(jié)點(diǎn)移動(dòng)没佑。
??2:新舊兩顆樹(shù)在進(jìn)行比較之后并不會(huì)直接去更新舊樹(shù),而是生成一個(gè)個(gè)patch温赔,然后執(zhí)行方法根據(jù)patch去更新舊樹(shù)蛤奢。(在推薦的第一篇文章中由于并沒(méi)有兩顆virtual dom樹(shù),用的virtual dom直接和html節(jié)點(diǎn)比較陶贼,所以它是是直接更新了舊樹(shù))啤贩。
??3:由于react的virtual dom有多種形式,原生的html element標(biāo)簽拜秧,自定義的component痹屹,字符串,實(shí)際上的dom比較還是比較繁瑣的枉氮,為了簡(jiǎn)化代碼理解志衍,所以這里在后面的代碼中默認(rèn)都是同類(lèi)型的標(biāo)簽數(shù)組。
這里定義簡(jiǎn)單一個(gè)TreeNode
export type ReactNode = {
tag: string;
attrs: null | object;
children: ReactNode[] | string[];
};
然后寫(xiě)diff方法聊替,這里由于咱默認(rèn)沒(méi)有string類(lèi)型以及component類(lèi)型的節(jié)點(diǎn)楼肪,所以我們只需要比較當(dāng)新節(jié)點(diǎn)的tag類(lèi)型和舊節(jié)點(diǎn)的tag類(lèi)型是否一致。不一樣的就刪了重新建立佃牛。
const diffNode = (preNode: ReactNode, currentNode: ReactNode) => {
const preNodeT = preNode as ReactNode;
const curNode = currentNode as ReactNode;
// tag類(lèi)型不同時(shí)直接銷(xiāo)毀原node淹辞,創(chuàng)建新node。
if (!preNode || preNodeT.tag.toLowerCase() !== curNode.tag.toLowerCase()) {
if (preNodeT) {
preNode = {
tag: curNode.tag,
attrs: curNode.attrs,
children: curNode.children
};
}
}
if (
(preNodeT.children && preNodeT.children.length > 0) ||
(curNode.children && curNode.children.length > 0)
) {
//關(guān)鍵方法是這個(gè)俘侠。
diffChildren(preNodeT, curNode.children);
}
};
關(guān)鍵的在同級(jí)比較以及同級(jí)節(jié)點(diǎn)交換的地方象缀,我相信搜索過(guò)diff并且看過(guò)一些diff的文章的人都熟悉這么一張同級(jí)比較的圖。
??如果用上述說(shuō)的不同則刪除的說(shuō)法來(lái)處理同級(jí)比較的話(huà)爷速,react diff算法就沒(méi)啥好講了央星,事實(shí)上,在同級(jí)計(jì)算中用key作為element的唯一標(biāo)識(shí)符惫东,在新舊node的key相同的一些情況下可以直接移動(dòng)節(jié)點(diǎn)而不用刪除再創(chuàng)建節(jié)點(diǎn)莉给。
const diffChildren = (preChildren: ReactNode[], currentChildren: ReactNode[]) => {
const preKeyArray:string[] = preChildren.map(node => {
return node.attrs['key'];
});
if (currentChildren && currentChildren.length > 0) {
// lastIndex 作為舊樹(shù)中已經(jīng)比較過(guò)的節(jié)點(diǎn)最右的位置,當(dāng)當(dāng)前訪(fǎng)問(wèn)節(jié)點(diǎn)在舊樹(shù)中的位置比之前比較過(guò)的節(jié)點(diǎn)都大時(shí)廉沮,
//說(shuō)明這個(gè)節(jié)點(diǎn)并不會(huì)影響其他節(jié)點(diǎn)的位置颓遏,放著就好不用操作。
let lastIndex = 0;
// 新樹(shù)是用來(lái)遍歷的滞时,preIndex 就是作為舊樹(shù)的地址下標(biāo)叁幢。
let preIndex = 0;
currentChildren.forEach((currentNode,currentIndex) => {
const currentKey = currentNode["key"];
// 新樹(shù)的key同時(shí)存在于舊樹(shù)
if (preKeyArray.includes(currentKey)) {
const preIndex = preKeyArray.indexOf(currentKey)
// 僅當(dāng)當(dāng)前舊樹(shù)中的節(jié)點(diǎn)位置比lastIndex小的時(shí)候需要移動(dòng)節(jié)點(diǎn)
// 因?yàn)檫@意味著這個(gè)節(jié)點(diǎn)的新位置將會(huì)破壞舊位置的順序,所以需要進(jìn)行移動(dòng)
if (preIndex < lastIndex) {
moveChild(preIndex,currentIndex)
}
lastIndex = Math.max(preIndex, lastIndex);
}
// 新樹(shù)的key不存在舊樹(shù)的時(shí)候
else{
// 舊樹(shù)同位置節(jié)點(diǎn)存在則刪除舊節(jié)點(diǎn)并且創(chuàng)建節(jié)點(diǎn)
// 否則直接創(chuàng)建節(jié)點(diǎn)
if (preChildren[currentIndex]) {
lastIndex = Math.max(currentIndex, lastIndex);
removeChild(preChildren[currentIndex])
}
createChild(currentNode,preIndex);
}
// 遍歷整棵樹(shù)坪稽。
diffNode(preChildren[preIndex],currentChildren[currentIndex])
preIndex++;
});
// 這里注意了曼玩,在上述操作后鳞骤,你是沒(méi)法刪除舊節(jié)點(diǎn)存在而新節(jié)點(diǎn)不存的節(jié)點(diǎn)的,最后遍歷一遍刪除黍判。
// 上面放的那個(gè)文章1就忘了刪除操作豫尽。
preChildren.forEach(item=>{
if (!currentKeyArray.includes(item.attrs['key'])) {
removeChild(item)
}
});
}
};
??基本的diff大概就是這樣了,雖然這里還有很多類(lèi)似與removeChild顷帖,createChild之類(lèi)的方法沒(méi)有實(shí)現(xiàn)美旧,但說(shuō)實(shí)話(huà)要完全實(shí)現(xiàn)的話(huà)還是比較復(fù)雜的,咱也不可能真就復(fù)制一份react代碼唄窟她。而且現(xiàn)在react開(kāi)始使用fiber結(jié)構(gòu)(就是說(shuō)這個(gè)方法過(guò)時(shí)了= =)陈症,上面文章2的代碼都得去react之前的release去找了...
??但是(震聲)!咱也不是白看的diff啊震糖,咱至少知道了key有啥用不是录肯。
??寫(xiě)代碼要是細(xì)一點(diǎn),就該把所有的位置有可能變化的列表數(shù)據(jù)都給他添上唯一性ID吊说,即使后臺(tái)給的數(shù)據(jù)论咏,我們也可以在獲得數(shù)據(jù)的同時(shí)自己給他加一個(gè)不變的唯一性id,不加的話(huà)颁井,這可都是新建以及銷(xiāo)毀的開(kāi)銷(xiāo)是吧厅贪。