mini-react 实现原理讲解 第五讲

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

复用子节点升级版 - key

初始渲染
...
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

相关文章
相关标签/搜索