是否学习该章对后续章节的学习没有影响。

    beginWork一节我们提到

    这一章我们讲解Diff算法的实现。

    为了防止概念混淆,这里再强调下

    一个DOM节点在某一时刻最多会有4个节点和他相关。

    1. current Fiber。如果该DOM节点已在页面中,current Fiber代表该DOM节点对应的Fiber节点

    2. DOM节点本身。

    3. JSX对象。即ClassComponentrender方法的返回结果,或FunctionComponent的调用结果。JSX对象中包含描述DOM节点的信息。

    Diff算法的本质是对比1和4,生成2。

    由于Diff操作本身也会带来性能损耗,React文档中提到,即使在最前沿的算法中,将前后两棵树完全比对的算法的复杂程度为 O(n 3 ),其中n是树中元素的数量。

    如果在React中使用了该算法,那么展示1000个元素所需要执行的计算量将在十亿的量级范围。这个开销实在是太过高昂。

    为了降低算法复杂度,Reactdiff会预设三个限制:

    1. 只对同级元素进行Diff。如果一个DOM节点在前后两次更新中跨越了层级,那么React不会尝试复用他。

    2. 两个不同类型的元素会产生出不同的树。如果元素由div变为,React会销毁div及其子孙节点,并新建p及其子孙节点。

    如果没有keyReact会认为div的第一个子节点由p变为h3,第二个子节点由h3变为p。这符合限制2的设定,会销毁并新建。

    但是当我们用key指明了节点前后对应关系后,React知道key === "ka"p在更新后还存在,所以DOM节点可以复用,只是需要交换下顺序。

    这就是React为了应对算法性能瓶颈做出的三条限制。

    Diff是如何实现的

    我们从Diff的入口函数reconcileChildFibers出发,该函数会根据newChild(即JSX对象)类型调用不同的处理函数。

    1. // 根据newChild类型选择不同diff函数处理
    2. function reconcileChildFibers(
    3. returnFiber: Fiber,
    4. currentFirstChild: Fiber | null,
    5. newChild: any,
    6. ): Fiber | null {
    7. const isObject = typeof newChild === 'object' && newChild !== null;
    8. // object类型,可能是 REACT_ELEMENT_TYPE 或 REACT_PORTAL_TYPE
    9. switch (newChild.$$typeof) {
    10. case REACT_ELEMENT_TYPE:
    11. // 调用 reconcileSingleElement 处理
    12. // // ...省略其他case
    13. }
    14. }
    15. if (typeof newChild === 'string' || typeof newChild === 'number') {
    16. // 调用 reconcileSingleTextNode 处理
    17. // ...省略
    18. if (isArray(newChild)) {
    19. // 调用 reconcileChildrenArray 处理
    20. // ...省略
    21. }
    22. // 一些其他情况调用处理函数
    23. // ...省略
    24. // 以上都没有命中,删除节点
    25. return deleteRemainingChildren(returnFiber, currentFirstChild);
    26. }

    我们可以从同级的节点数量将Diff分为两类:

    1. newChild类型为objectnumberstring,代表同级只有一个节点

    2. newChild类型为Array,同级有多个节点