美文网首页
React深入9-源码5(Diff)

React深入9-源码5(Diff)

作者: 申_9a33 | 来源:发表于2022-03-15 18:50 被阅读0次

1.diff 策略

  • 1.同级比较,Web UI中Dom节点跨层移动比较少,可以忽略不计
  • 2.拥有不同类型的两个组件将会生成不同的树形结构
  • 3.开发者可以通过keyprop来暗示那些子元素在不同的渲染下能保持稳定

2.diff过程

  • 删除:newVnode不存在时
  • 替换:vnode和newVnode类型不同或者key不同时
  • 更新:有相同的类型和key,但是vnode和newVnode不同时

3.diff 流程图

diff.png
  • newIndex从0到children.length,新老节点只会比对一次,算法复杂度为O(n)
  • 第一步,从第一个新老节点开始是否能被复用,不能则终止
  • 第二步,children如果遍历完,则将当前的开始的老节点全部放入父节点的deletions上
  • 第三步,老节点已经遍历完,将甚于的新节点放入新的链表中
  • 第四步,兜底操作,将剩余的老节点放入map中,判断从现在开始的新节点,map中是否存在,存在复用,不存在创建。所有新节点创建完成后,将map中剩余的老节点放入父节点的deletions上

4.实现

4.1 diff核心实现(协调,reconcile)

// src\react\ReactChildFiber.ts

import { createFiber, IFiber } from './createFiber';
import { isStringOrNumber, Placement, Update } from './utils';

/**
 * old 1,2,3,4
 * new 2,4,5
 * 协调子节点
 * */
export function reconcileChildren(returnFiber:IFiber, children:any) {
  if (isStringOrNumber(children)) {
    return;
  }

  const shouldTrackSideEffects = !!returnFiber.alternate;

  const newChildren = Array.isArray(children) ? children : [children];

  let previousNewFiber:IFiber | null = null;
  let oldFiber:any = returnFiber.alternate && returnFiber.alternate.child;
  let nextOldFiber = null;

  let newIndex = 0;
  let lastPlacedIndex = 0;

  // 1.更新阶段,找到能复用的节点,如果不能复用,则停止这轮循环
  for (;oldFiber && newIndex < newChildren.length; newIndex++) {
    const newChild = newChildren[newIndex];
    if (newChild === null || newChild === false) {
      // 不存在结束当前循环
      continue;
    }
    if (oldFiber.index > newIndex) {
      nextOldFiber = oldFiber;
      oldFiber = null;
    } else {
      nextOldFiber = oldFiber.sibling;
    }

    const newFiber = createFiber(newChild, returnFiber);
    Object.assign(newFiber, {
      alternate: oldFiber,
      stateNode: oldFiber?.stateNode,
      flags: Update,
    });

    const same = sameNode(newFiber, oldFiber);
    if (!same) {
      if (oldFiber === null) {
        oldFiber = nextOldFiber;
      }
      break;
    }

    lastPlacedIndex = placeChild(
      newFiber,
      lastPlacedIndex,
      newIndex,
      shouldTrackSideEffects,
    );

    if (previousNewFiber === null) {
      returnFiber.child = newFiber;
    } else {
      previousNewFiber.sibling = newFiber;
    }
    previousNewFiber = newFiber;
    oldFiber = nextOldFiber;
  }

  // 2.校验newChildren是否到头,如果newChildren已经遍历完,那么把剩下的oldFiber链表删除就行
  if (newIndex === newChildren.length) {
    deleteRemainingChildren(returnFiber, oldFiber);
  }

  // 3.
  // 3.1 初次渲染
  // 3.2 oldFiber已经为null,也就意味着老fiber链表已经被复用完成,但是新的Children还有fiber要生成,就只能新增插入了
  if (!oldFiber) {
    for (; newIndex < newChildren.length; newIndex++) {
      const newChild = newChildren[newIndex];
      if (newChild === null) {
        continue;
      }
      const newFiber = createFiber(newChild, returnFiber);
      lastPlacedIndex = placeChild(
        newFiber,
        lastPlacedIndex,
        newIndex,
        shouldTrackSideEffects,
      );

      if (previousNewFiber === null) {
        returnFiber.child = newFiber;
      } else {
        previousNewFiber.sibling = newFiber;
      }
      previousNewFiber = newFiber;
    }
    return;
  }

  // 4.同时遍历新子节点数组和老链表,能复用的节点就服用,没法复用的老节点删除,新节点新增插入
  // old 1:{} 2:{} 3:{} 4:{} (查找,删除)
  // new 2 4 5
  const existingChildren = mapRemainingChildren(oldFiber);
  for (; newIndex < newChildren.length; newIndex++) {
    const newChild = newChildren[newIndex];
    if (newChild === null || newChild === false) {
      continue;
    }

    const newFiber = createFiber(newChild, returnFiber);
    lastPlacedIndex = placeChild(
      newFiber,
      lastPlacedIndex,
      newIndex,
      shouldTrackSideEffects,
    );

    // 从老链表上找到能复用的节点
    const matchedFiber = existingChildren.get(newFiber.key || newFiber.index);
    if (matchedFiber) {
      // 找到能复用的节点
      existingChildren.delete(newFiber.key || newFiber.index);
      Object.assign(newFiber, {
        alternate: matchedFiber,
        stateNode: matchedFiber.stateNode,
        flags: Update,
      });
    }

    if (previousNewFiber === null) {
      returnFiber.child = newFiber;
    } else {
      previousNewFiber.sibling = newFiber;
    }
    previousNewFiber = newFiber;
  }

  if (shouldTrackSideEffects) {
    existingChildren.forEach((child:IFiber) => deleteChild(returnFiber, child));
  }
}

function placeChild(
  newFiber:IFiber,
  lastPlacedIndex:number,
  newIndex:number,
  shouldTrackSideEffects:boolean,
):number {
  newFiber.index = newIndex;
  if (!shouldTrackSideEffects) {
    return lastPlacedIndex;
  }
  const current = newFiber.alternate;

  if (current) {
    const oldIndex = current.index || 0;
    if (oldIndex < lastPlacedIndex) {
      // move
      newFiber.flags = Placement;
      return lastPlacedIndex;
    }
    return oldIndex;
  }
  newFiber.flags = Placement;
  return lastPlacedIndex;
}

// 删除单个节点
function deleteChild(returnFiber:IFiber, childToDelete:IFiber) {
  // returnFiber.deletoins = [...]
  const { deletoins } = returnFiber;
  if (deletoins) {
    deletoins.push(childToDelete);
  } else {
    returnFiber.deletoins = [childToDelete];
  }
}

// 删除节点链表,头节点是currentFirstChild
function deleteRemainingChildren(returnFiber:IFiber, currentFirstChild:IFiber) {
  let childToDelete:any = currentFirstChild;
  while (childToDelete) {
    deleteChild(returnFiber, childToDelete);
    childToDelete = childToDelete.sibling;
  }
}

function mapRemainingChildren(currentFirstChild:IFiber) {
  const existingChildren = new Map();
  let existingChild:any = currentFirstChild;
  while (existingChild) {
    existingChildren.set(
      existingChild.key || existingChild.index,
      existingChild,
    );
    existingChild = existingChild.sibling;
  }

  return existingChildren;
}

function sameNode(a:any, b:any) {
  return !!(a && b && a.type === b.type && a.key === b.key);
}

4.2 调整src\react\ReactFiberWorkLoop.ts的新建和删除

  // 新增
  if (stateNode && flags & Placement) {
    const hasSiblingNode = foundSiblingNode(wip, parentNode);
    if (hasSiblingNode) {
      parentNode.insertBefore(stateNode, hasSiblingNode);
    } else {
      parentNode.appendChild(wip.stateNode);
    }
  }

  // 删除
  if (wip.deletoins) {
    commitDeletions(wip.deletoins, stateNode || parentNode);
    wip.deletoins = null;
  }

源码

相关文章

网友评论

      本文标题:React深入9-源码5(Diff)

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