2-2-12 React原理基础 (DOM - DIFF )部分

WHY DOM-DIFF?

我们将一个React函数看做是一个返回VirtualDOM Tree的方法:

function SomeComponent() {        
    return 
...
}

上面的JSX会被转化成树状结构:

div
 span
 Button

当箭头函数的返回值发生变化的时候,比如(新增了某个节点):

function SomeComponent({a}) {        
    return 
... {a &&

新增的节点

}
}

我们可以有很多的策略完成这样的更新,比如把所有节点都删了,再重新构造。

当然,**最划算**的渲染策略,是在`div` 下新增一个`p` ,而不是将整个DOM替换。

React的调和算法帮助我们找到这种最快的更新途径。

伪代码

具体来说,React渲染函数的执行,会产生一个树状结构:

// 会被转化为 React.createElement('div', { style : { ... } })

`createElement` 后形成一个VirtualDOM节点

class ReactElement {
    type : SomeComponent   
    props : {
        children
    } 
    
}

Element可以看做“数据”的描述, 也就是元数据。 Element可以看做是虚拟DOM,代表了真实的DOM结构。因为每次渲染函数调用都生成一个新的Element,因此在Element之上还需要一个封装。

例如(伪代码):

class FiberNode {
    type : SomeComponent
    props : ...
    
    update(...) 
}

同一个渲染函数的两次执行,究竟产生哪些DOM操作,需要由DOM-DIFF模块确定

具体来说,对于某个给定的组件:

function SomeComponent(...) {
    return 
...
}

当组件`SomeComponent`触发更新的时候,`react` 会这样处理:

... Fiber Context {
    
    let vDOMOld  // 上一次调用SomeComponent产生的virtualDOM
    ……
    update() {
        const vdomNext = SomeComponent(...)
        const updates = domDiff(vDOMOld, vDOMNext)
        vDOMOld = vDOMNext
        apply(updates)
    }
}

React更新产生虚拟DOM节点,然后通过diff算法比较两个DOM节点的差异,决定更新步骤,最后再向DOM应用这些更新。

在上面的程序中,箭头函数的返回值会被React记录下来,下次更新会尝试使用这个值继续比较。

这里有一个要求,domDiff算法的效率必须足够高,因为所有的更新都依赖它

DOM-DIFF细节

对于类型相同的节点

if ( vDOMOld.type === vDOMNext.type)

对于类型相同的节点,比如:

function Button({text}) {
    return 
}

当发生变化的时候:

只需要替换属性即可,React遇到这种情况,会帮用户替换掉属性。

不同类型的节点

当遇到不同类型的节点,React会直接替换。

// 到

子节点的处理

对于子节点, React会进行对比和替换。

  • first
  • second
// 到
  • first
  • second
  • third

React会只insert第三个li。

但是对于下面这种乱序的情况,React会逐一替换:

  • Duke
  • Villanova
  • Connecticut
  • Duke
  • Villanova

这是因为DOM-DIFF会用简单的算法,顺序比较,而不是用动态规划。在比较一个列表的时候,用最短编辑距离(动态规划)后,仍然有昂贵的DOM操作(插入、删除)等。而动态规划算法的复杂度有O(n^2)。

为了解决比较慢的问题,React引入了key来解决:

  • Duke
  • Villanova
// 到
  • Connecticut
  • Duke
  • Villanova

上面程序中,React会用key来进行比较,上面程序会考虑到key相同的情况,认为key相同的是同一个节点。 因此2014会是一个插入的节点。

伪代码

function* domDIFF(vDOM1, vDOM2) {
    if(!vDOM1) {
        yield new InsertUpdate(vDOM1, vDOM2)
        return
    }
    
        
    if(vDOM1.type === vDOM2.type) {
        if(vDOM1.key === vDOM2.key) {            
          yield new AttributeUpdate(vDOM1, vDOM2)                
          yield * domDIFFArray(vDOM1.children, vDOM2.children)
        } else {
          yield new ReplaceUpdate(vDOM1, vDOM2)
        }
        return
    } else {
        yield new ReplaceUpdate(vDOM1, vDOM2)
    }
 
}



function toMap(arr) {
    const map = new Map()
    arr.forEach(item => {
        if(item.key)
          map.set(item.key, item)  
    })
    return map
}

function * domDiffArray(arr1, arr2) {
    if(!arr1 || !arr2) {
        yield new ReplaceUpdate(vDOM1, vDOM2)
        return
    }

    const m1 = toMap(arr1)
    const m2 = toMap(arr2)
    
    // 需要删除的VDOM
    const deletes = arr1.filter( (item, i) => {        
        return item.key ? 
            !m2.has(item.key)
           	: i >= arr2.length
    })
    
    for(let item of deletes){
        yield new ReplaceUpdate(item, null)
    }
    
    // 需要Replace的VDOM    
    for(let i = 0; i = arr1.length) {
            yield new InsertUpdate(i, arr[2])
          }
        }
    }


}

class InsertUpdate {    
    constructor(pos, to){
        this.pos = pos
        this.to = to
    }
}


class ReplaceUpdate {
    constructor(from, to){
        this.form = from 
        this.to = to
    }
}

1

你可能感兴趣的:(web,架构,react.js,javascript,前端)