源码-reconcile的过程

2025-3-1 1,467 3/1

源码,过一下diff算法的细节!!!!!

入口位置 react/packages/react-reconciler/src/ReactFiberWorkLoop.new.js

这里源码做了删减,只保存了主流程

function workLoopConcurrent() {
  while (workInProgress !== null && !shouldYield()) {
    performUnitOfWork(workInProgress);
  }
}

function performUnitOfWork(unitOfWork: Fiber): void {
// alternate指向的是另一颗fiber树映射的节点,注意下这一点,在后面处理函数组件中有用到
  const current = unitOfWork.alternate;
  setCurrentDebugFiberInDEV(unitOfWork);
  let next;
    next = beginWork(current, unitOfWork, renderLanes);
  if (next === null) {
    completeUnitOfWork(unitOfWork);
  } else {
    workInProgress = next;
  }
  ReactCurrentOwner.current = null;
}

beginWork 即向下递的过程,生成fiber节点,diff,复用,打上自身节点的tag

这里就对不同的组件做了不同的处理,例如根节点,函数组件,类组件,memo,fowordRef,文本节点等,但是逻辑基本都是差不多的,就只看下函数组件咋处理的

function beginWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
): Fiber | null {
witch (workInProgress.tag) {
case FunctionComponent: {
  // type其实就是我们的那个函数,并不是createElement
  const Component = workInProgress.type;
  return updateFunctionComponent(
  current,
  workInProgress,
  Component,
  resolvedProps,
  renderLanes,
);
}
  // 其他 case 语句...
}} 

updateFunctionComponent

处理函数组件 renderWithHooks执行了我们的组件函数,reconcileChildren,处理的children,也就是diif阶段(这里只会处理sibling,并不会处理sibling的子节点),然后返回当前节点的firstChild

function updateFunctionComponent(
  current,
  workInProgress,
  Component,
  nextProps: any,
  renderLanes,
) {
nextChildren = renderWithHooks(
  current,
  workInProgress,
  Component,
  nextProps,
  context,
  renderLanes,
);
workInProgress.flags |= PerformedWork;
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}

renderWithHooks重置fiber的memoizedState与updateQueue即保存hooks数据的链表和副作用队列

然后根据current,来判断是否是挂载 还是更新,current是当前页面展示的这一棵fiber树workInProgress是我们在缓存中处理的这颗fiber树。也就是判断当前fiber对应的另一个fiber是否存在或者缓存的另一个fiber节点的hooks链表是否为空(这里需要了解下fiber的数据结构啊,const current = unitOfWork.alternate;alternate指向了另一颗fiber映射的节点)

然后执行了我们的组件函数,在重置ReactCurrentDispatcher.current为默认,最后返回children

// 进入
function renderWithHooks(
current,
workInProgress,
Component,
nextProps,
context,
renderLanes,
){
workInProgress.memoizedState = null;
workInProgress.updateQueue = null;
workInProgress.lanes = NoLanes;
ReactCurrentDispatcher.current =
current === null || current.memoizedState === null
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate;
}
let children = Component(props, secondArg);
ReactCurrentDispatcher.current = ContextOnlyDispatcher;
return children;
}

向下递的流程

reconcileChildren看下diff算法,这里只关注下childrn为Array的情况

function reconcileChildFibers(
  returnFiber: Fiber,
  currentFirstChild: Fiber | null,
  newChild: any,
  lanes: Lanes,
): Fiber | null {
  const isUnkeyedTopLevelFragment =
    typeof newChild === 'object' &&
    newChild !== null &&
    newChild.type === REACT_FRAGMENT_TYPE &&
    newChild.key === null;
  if (isUnkeyedTopLevelFragment) {
    newChild = newChild.props.children;
  }
    if (isArray(newChild)) {
      return reconcileChildrenArray(
        returnFiber,
        currentFirstChild,
        newChild,
        lanes,
      );
    }

    if (getIteratorFn(newChild)) {
      return reconcileChildrenIterator(
        returnFiber,
        currentFirstChild,
        newChild,
        lanes,
      );
    }

    throwOnInvalidObjectType(returnFiber, newChild);
  }
  return deleteRemainingChildren(returnFiber, currentFirstChild);
}

diff算法
两遍for循环

在这个过程中,给当前fiber节点打上 tag(增,删,改)

updateSlot,对比 vdom 和老的 fiber根据节点的key和type对比,如果相同则复用老的fiber节点(会更改一些属性),在遍历过程中发现不同则结束第一次遍历,并记录下结束时fiber指针和children的index。如果所有的新的 vdom 处理完了,那就把剩下的老 fiber 节点删掉就行。

如果还有 vdom 没处理,就进行第二次遍历,把剩下的老 fiber 放到 map 里,遍历剩下的 vdom,从 map 里查找,如果找到了,就移动过来,打上修改的标记(因为这时他们的顺序已经变了)。

第二轮遍历完了之后,把剩余的老 fiber 删掉,剩余的 vdom 新增。

function reconcileChildrenArray(
  returnFiber: Fiber,
  currentFirstChild: Fiber | null,
  newChildren: Array<*>,
  lanes: Lanes,
): Fiber | null {
  let resultingFirstChild: Fiber | null = null;
  let previousNewFiber: Fiber | null = null;

  let oldFiber = currentFirstChild;
  let lastPlacedIndex = 0;
  let newIdx = 0;
  let nextOldFiber = null;
// 第一轮遍历,复用fiber节点
  for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
    if (oldFiber.index > newIdx) {
      nextOldFiber = oldFiber;
      oldFiber = null;
    } else {
      nextOldFiber = oldFiber.sibling;
    }
    const newFiber = updateSlot(
      returnFiber,
      oldFiber,
      newChildren[newIdx],
      lanes,
    );
    if (newFiber === null) {
      if (oldFiber === null) {
        oldFiber = nextOldFiber;
      }
      break;
    }
    if (shouldTrackSideEffects) {
      if (oldFiber && newFiber.alternate === null) {
        deleteChild(returnFiber, oldFiber);
      }
    }
    lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
    if (previousNewFiber === null) {
      resultingFirstChild = newFiber;
    } else {
      previousNewFiber.sibling = newFiber;
    }
    previousNewFiber = newFiber;
    oldFiber = nextOldFiber;
  }

  if (newIdx === newChildren.length) {
    deleteRemainingChildren(returnFiber, oldFiber);
    if (getIsHydrating()) {
      const numberOfForks = newIdx;
      pushTreeFork(returnFiber, numberOfForks);
    }
    return resultingFirstChild;
  }

  if (oldFiber === null) {
    // since the rest will all be insertions.
    for (; newIdx < newChildren.length; newIdx++) {
      const newFiber = createChild(returnFiber, newChildren[newIdx], lanes);
      if (newFiber === null) {
        continue;
      }
      lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
      if (previousNewFiber === null) {
        // TODO: Move out of the loop. This only happens for the first run.
        resultingFirstChild = newFiber;
      } else {
        previousNewFiber.sibling = newFiber;
      }
      previousNewFiber = newFiber;
    }
    if (getIsHydrating()) {
      const numberOfForks = newIdx;
      pushTreeFork(returnFiber, numberOfForks);
    }
    return resultingFirstChild;
  }
// 余下fiber节点转map(oldFiber时第一轮遍历结束时,指向的fiber节点)
  const existingChildren = mapRemainingChildren(returnFiber, oldFiber);
// 第二轮遍历,从map中根据我们设置的key去找
  for (; newIdx < newChildren.length; newIdx++) {
    const newFiber = updateFromMap(
      existingChildren,
      returnFiber,
      newIdx,
      newChildren[newIdx],
      lanes,
    );
    if (newFiber !== null) {
      if (shouldTrackSideEffects) {
        if (newFiber.alternate !== null) {
          existingChildren.delete(
            newFiber.key === null ? newIdx : newFiber.key,
          );
        }
      }
      lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
      if (previousNewFiber === null) {
        resultingFirstChild = newFiber;
      } else {
        previousNewFiber.sibling = newFiber;
      }
      previousNewFiber = newFiber;
    }
  }

  if (shouldTrackSideEffects) {
    existingChildren.forEach(child => deleteChild(returnFiber, child));
  }
  if (getIsHydrating()) {
    const numberOfForks = newIdx;
    pushTreeFork(returnFiber, numberOfForks);
  }
  return resultingFirstChild;
}

递到低,向上冒泡

调用completeWork处理当前节点

处理兄弟节点,如果兄弟节点存在则更改workInProgress指针指向(即兄弟节点开始beginWork流程)

function completeUnitOfWork(unitOfWork: Fiber): void {
  let completedWork = unitOfWork;
  do {
    const current = completedWork.alternate;
    const returnFiber = completedWork.return;
 // 主流程 
    let next = completeWork(current, completedWork, renderLanes);
      if (next !== null) {
        workInProgress = next;
        return;
      }
    const siblingFiber = completedWork.sibling;
    if (siblingFiber !== null) {
      workInProgress = siblingFiber;
      return;
    }
    completedWork = returnFiber;
    workInProgress = completedWork;
  } while (completedWork !== null);
  if (workInProgressRootExitStatus === RootInProgress) {
    workInProgressRootExitStatus = RootCompleted;
  }
}

completeWork

主要看下HostComponent类型的处理,也就是我们的html元素类型

stateNode即fiber节点映射的真实dom,如果存在的话做则根据props对原先的dom做更新操作,

不存在的话则需要创建,然后所有子节点添加到新创建的实例中

向上冒泡副作用标记,确保父节点能够知晓子节点的变化(也就是计算父节点的subTreeTag)

function completeWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
): Fiber | null {
  const newProps = workInProgress.pendingProps;
switch (workInProgress.tag) {
case HostComponent: {
popHostContext(workInProgress);
const type = workInProgress.type;
if (current !== null && workInProgress.stateNode != null) {
updateHostComponent(current, workInProgress, type, newProps);
if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
}
} else {
const currentHostContext = getHostContext();
const wasHydrated = popHydrationState(workInProgress);
if (wasHydrated) {
if (
prepareToHydrateHostInstance(workInProgress, currentHostContext)
) {
markUpdate(workInProgress);
}
} else {
const rootContainerInstance = getRootHostContainer();
const instance = createInstance(
type,
newProps,
rootContainerInstance,
currentHostContext,
workInProgress,
);
appendAllChildren(instance, workInProgress, false, false);
workInProgress.stateNode = instance;
if (
finalizeInitialChildren(
instance,
type,
newProps,
currentHostContext,
)
) {
markUpdate(workInProgress);
}
}

if (workInProgress.ref !== null) {
markRef(workInProgress);
}
}
bubbleProperties(workInProgress);
return null;
}
}

OK!

- THE END -
1

共有 0 条评论