文章首发于个人博客 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