react-diff算法详解

作者: mytac | 来源:发表于2019-03-28 18:17 被阅读23次

    前言

    本文是我阅读《深入React技术栈》所写的总结笔记。如果您觉得本站的markdown代码高亮不友好,建议您查看:原文

    reconciliation调和,是react中最为核心的模块,它指的是将virtual dom树转换成actual dom树所耗费的最少操作。他需要进行diff->patch这两个过程。diff是计算virtual dom 树转换成另一棵树进行的最少操作,而patch是将差异更新到真实的dom节点。

    diff

    tree diff

    react为了让运行效率更高,tree diff只对树进行同层对比,不去比较跨层的节点。比如,在树A中第一层有一个节点B,想要将他移动到第二层,但并不会直接移动。而是会在第二层创建节点B,接着创建节点B的子节点,创建节点B的子节点的子节点...,然后再把之前第一层的B节点删除。

    <A>         <A>
    <B/>        <C>
    <C/>        <B/>
    </A>   ->   </C>
                </A>
    
    demo

    component diff

    因为react通过组件化开发,在对比组件差异上也采用上述算法。即,同一层只要出现不是同一类型的组件,就替换该组件的所有子节点。对于同一类型的组件,则通过shouldComponentUpdate去判断是否需要通过diff进行分析。shouldComponentUpdate默认为true。

    element diff

    element diff主要是根据mountIndexlastIndex进行比较,在确定是否移动 ,mountIndex是A节点在旧节点结合中的位置,lastIndex指访问过的节点,在旧集合中最右的位置,每次遍历都有可能会更新。

    算法描述

    1. 遍历新节点集合

    2. 如果出现旧节点集合中有与当前指针所指新节点A相同的节点,则通过对比节点位置进行判断操作,对比mountIndexlastIndex

      如果mountIndex>=lastIndex:不做移动操作。并把lastIndex更新为mountIndex

      如果mountIndex<lastIndex:移动。

    3. 如果新节点集合中有旧节点集合中不存在的节点,添加,更新lastIndex

    4. 最后遍历旧节点集合,如果存在新节点集合上不存在的点,则将其删除。

    至于为什么要比较mountIndexlastIndex,是因为要保证当前要进行移动操作的节点一定要比lastIndex小,一是为了节约性能,二是为了使节点排序更有条理,如果不进行比较,看见有相同的节点就移动,整个队列就乱了套了。

    Tips:React中有提示说,要尽量避免将最后一个节点移动到第一个节点的操作。就是因为在一上来比较的时候,本来只需要将最后一个节点移动到第一个位置这一个操作。但按照diff算法的逻辑,mountIndex为最大值,所以lastIndex也更新为最大值,第一个节点之后的节点都需要进行移动操作。

    不太明白的同学可以参考这篇文章->《React之diff算法》,里面有分步图文描述,更便于理解。

    差异队列

    在上一小节中,我们已经知道了diff是如何判断哪些节点要移动,哪些节点要删除或新增,这些修改的内容都被加入了差异队列当中。其中这三种节点操作,分别对应三种type:(在这之前通过了flattenChildren方法将子节点扁平化,key值相同的只取最后一个节点)

    INSERT_MARKUP: 旧集合中有不存在的组件类型或节点,需要对组件或节点进行插入操作
    
    MOVE_EXISTING: 源码中要对比prevChild===nextChild,即旧集合中有与新集合完全一样的节点,书中说是类型相同且element是可更新的,复用以前的dom节点。
    
    REMOVE_NODE: 旧组件类型在新集合中也存在,但对应的element不同,不能更新和复用。或者旧组件中存在新集合中不存在的,也需要进行删除操作。
    

    源码中有三个函数makeInsertMarkupmakeMovemakeRemove,用来返回上述三个操作对象。(大家可以把这里看作reduxaction的概念)。如下,是进行新增操作的对象

    {
        type: ReactMultiChildUpdateTypes.INSERT_MARKUP,
        content: markup,
        fromIndex: null,
        fromNode: null,
        toIndex: toIndex,
        afterNode: afterNode,
    }
    

    在遍历的过程中,react不会一发现需要更新的节点就立即更新到真实dom上,而是将所有的上述差异对象,全部放入差异队列中,然后通过patch再将其更新到真实的dom上。

    patch

    patch是指遍历差异队列依次更新到真实dom上的操作。通过switch去匹配差异对象的type,然后进行对应的操作。
    =>patch源码在这里

    参考文档

    1. react源码--renderers/shared/reconciler/ReactMultiChild.js
    2. React源码之Diff算法

    相关文章

      网友评论

        本文标题:react-diff算法详解

        本文链接:https://www.haomeiwen.com/subject/lfpcbqtx.html