文章首發於個人博客 https://github.com/mcuking/bl...相關代碼請查閱 https://github.com/mcuking/bl...node
以前操做子節點的代碼:react
for(let i = 0; i < vnode.children.length; i++) { render(vnode.children[i], dom, null, null) }
render 的第 3 個參數 comp '誰渲染了我', 第 4 個參數 olddom '以前的舊 dom 元素'。如今複用舊的 dom, 因此第 4 個參數多是有值的 代碼以下:git
let olddomChild = olddom.firstChild for(let i = 0; i < vnode.children.length; i++) { render(vnode.children[i], olddom, null, olddomChild) olddomChild = olddomChild && olddomChild.nextSibling } // 刪除多餘的子節點 while (olddomChild) { let next = olddomChild.nextSibling olddom.removeChild(olddomChild) olddomChild = next }
因此完整的 diff 機制以下(包括複用屬性 / 複用子節點):github
function diffDOM(vnode, parent, comp, olddom) { const {onlyInLeft, bothIn, onlyInRight} = diffObject(vnode.props, olddom.__vnode.props) setAttrs(olddom, onlyInLeft) removeAttrs(olddom, onlyInRight) diffAttrs(olddom, bothIn.left, bothIn.right) let olddomChild = olddom.firstChild for(let i = 0; i < vnode.children.length; i++) { render(vnode.children[i], olddom, null, olddomChild) olddomChild = olddomChild && olddomChild.nextSibling } while (olddomChild) { // 刪除多餘的子節點 let next = olddomChild.nextSibling olddom.removeChild(olddomChild) olddomChild = next } olddom.__vnode = vnode }
因爲須要在 diffDOM 的時候從 olddom 獲取 olddom._vnode(即 diffObject(vnode.props, olddom.__vnode.props))。 因此:app
// 在建立的時候 ... let dom = document.createElement(vnode.nodeName) dom.__vnode = vnode ... // diffDOM ... const {onlyInLeft, bothIn, onlyInRight} = diffObject(vnode.props, olddom.__vnode.props) ... olddom.__vnode = vnode // 更新完以後, 須要把__vnode 的指向 更新 ...
另外對於 TextNode 的複用:dom
... if(typeof vnode == "string" || typeof vnode == "number") { if(olddom && olddom.splitText) { if(olddom.nodeValue !== vnode) { olddom.nodeValue = vnode } } else { dom = document.createTextNode(vnode) if(olddom) { parent.replaceChild(dom, olddom) } else { parent.appendChild(dom) } } } ...
初始渲染 ... render() { return ( <div> <WeightCompA/> <WeightCompB/> <WeightCompC/> </div> ) } ... setState 再次渲染 ... render() { return ( <div> <span>hi</span> <WeightCompA/> <WeightCompB/> <WeightCompC/> </div> ) } ...
咱們以前的子節點複用順序就是按照 DOM 順序,顯然這裏若是這樣處理的話,可能致使組件都複用不了。 針對這個問題,React 是經過給每個子組件提供一個 key 屬性來解決的。對於擁有一樣 key 的節點,認爲結構相同。因此問題變成了:函數
f([{key: 'wca'}, {key: 'wcb'}, {key: 'wcc'}]) = [{key:'spanhi'}, {key: 'wca'}, {key: 'wcb'}, {key: 'wcc'}]
函數 f 經過刪除,插入操做,把 olddom 的 children 順序,改成和 newProps 裏面的 children 同樣(按照 key 值同樣)。spa
因爲經過 key 複用子節點實現略複雜,暫時擱置。code
相關文章blog