The DIFF core function — reconcileChildFibers

Purpose: Generate a new Fiber diff from the dirty check diff object: workInProgress’s oldChildrenFiber compared to newChildren (JSX). FragmentFiber cannot be reused: 1. The key is different. 2

NewChildren diff for different cases. I’m only going to look at three cases

Single JSX (newChildren is a single JSX) — reconcileSingleElement

  • * * * * cycleoldChildrenFiber.childFibernewChild(jsx)To compare
    • keyThe same
      • ifnewChildFragmentoldFiber.tagIs alsoFragment
        • Mark deletion residueoldChildrenFiberdeleteChild()
        • existing= Get ** multiplexed fiber **–useFiber()
          • Update index, ref, return and other attributes. (Dom reuse)
        • return existing
      • iftypeThe same
        • Mark deletion residueoldChildrenFiberdeleteChild()
        • existing= getMultiplexing fiberuseFiber()
          • Update index, ref, return and other attributes. (Dom reuse)
        • return existing
      • At this point, the statementkeyThe corresponding component cannot be reused
      • Mark deletion residueoldChildrenFiberdeleteChild()
    • keydifferent
      • Tags deleted childFiber  — deleteChild()
    • child = child.sibling
  • If I go here, I can’t reuse fibers.
  • Created = createThe new fibercreateFiberFromElement()
    • FragmentcreateFiberFromFragment()
  • return created
function reconcileSingleElement(
    returnFiber: Fiber,
    currentFirstChild: Fiber | null,
    element: ReactElement,
    lanes: Lanes,
  ) :Fiber {
    const key = element.key;
    let child = currentFirstChild;
    while(child ! = =null) { // fiber update
      if (child.key === key) { / / the same key
        const elementType = element.type;
        / / reuse fragments
        if (elementType === REACT_FRAGMENT_TYPE) {
          if (child.tag === Fragment) {
            / / delete
            deleteRemainingChildren(returnFiber, child.sibling);
            / / multiplexing fiber
            const existing = useFiber(child, element.props.children);
            existing.return = returnFiber;
            returnexisting; }}else {
          // Reuse other components of the same type, as well as lazy
          if ( 
            child.elementType === elementType ||
            (enableLazyElements &&
              typeof elementType === 'object'&& elementType ! = =null &&
              elementType.$$typeof === REACT_LAZY_TYPE &&
              resolveLazy(elementType) === child.type)
          ) {
            deleteRemainingChildren(returnFiber, child.sibling);
            const existing = useFiber(child, element.props);
            existing.ref = coerceRef(returnFiber, child, element);
            existing.return = returnFiber;
            returnexisting; }}// Delete all keys that match the same key
        deleteRemainingChildren(returnFiber, child);
        break;
      } else {
        / / delete
        deleteChild(returnFiber, child);
      }
      child = child.sibling;
    }
		// Create Fragment Fiber
    if (element.type === REACT_FRAGMENT_TYPE) {
      const created = createFiberFromFragment(
        element.props.children,
        returnFiber.mode,
        lanes,
        element.key,
      );
      created.return = returnFiber;
      return created;
    } else {
		// Create ReactElement Fiber
      const created = createFiberFromElement(element, returnFiber.mode, lanes);
      created.ref = coerceRef(returnFiber, currentFirstChild, element);
      created.return = returnFiber;
      returncreated; }}Copy the code

Text (when newChildren is string or Number) — reconcileSingleTextNode

Note: React optimizes for cases where the native chilren is a single text node and does not generate fiber for it

  • Do not generate fibet => updateHostComponent Set newChildren to NULL
  • Node inserts text => completeWork-> Case HostComponent -> finalizeInitialChildren -> setInitialProperties -> SetInitialDOMProperties -> Typeof nextProp === ‘String’ or ‘number’ -> setTextContent

Implementation:

  • iffirstChildFiberWhether it isText node
    • Mark delete restsiblingFiberdeleteChild()
      • existing= Get multiplexed fiber —useFiber()
        • Update attributes such as index and return. (Dom reuse)
    • return existing
  • If I go to this point, I can’t reuse it.
  • Tags deletedchildrenFiberdeleteChild()
  • Created = createThe new fibercreateFiberFromText()
  • return created
function reconcileSingleTextNode(
  returnFiber: Fiber,
  currentFirstChild: Fiber | null,
  textContent: string,
  lanes: Lanes,
) :Fiber {
  / / reuse
  if(currentFirstChild ! = =null && currentFirstChild.tag === HostText) {
    deleteRemainingChildren(returnFiber, currentFirstChild.sibling);
    const existing = useFiber(currentFirstChild, textContent);
    existing.return = returnFiber;
    return existing;
  }
  // Delete new
  deleteRemainingChildren(returnFiber, currentFirstChild);
  const created = createFiberFromText(textContent, returnFiber.mode, lanes);
  created.return = returnFiber;
  return created;
}
Copy the code

Array (The newChildren value is array, and the array item is corresponding to JSX) — reconcileChildrenArray

  • Note:
    • O(n) complexity;oldChildrenFibernewChildrenJSXIt’s only done once, and it’s done.
    • Node change priority: Update > Delete > Add > Position Change
  • Module variables:
    • resultingFirstChild: the final returnfiber
    • previousNewFiber: Newly creatednewFiber
    • lastPlacedIndex: The last Placementfiber
    • oldFiber: indicates the traversal being performedfiber, fromcurrentFirstChildtraversesiblingnull 。 
    • newChild: indicates the traversal being performedjsx. fromnewChildrenJSXandnewIdxSure.
    • newIdx: indicates the traversal being performednewChildThe index.
    1. With updates
    • What is an update? If the key does not change, it is updated. Try to reuse fiber and DOM when updating
      • If no key is written, the default value is null. Both the old and new keys are null, and the key remains unchanged.
    • cycle oldChildrenFiber支那and newChildrenJSX* *.childFibernewChild(jsx)To compare
      • generatenewFiberupdateSlot()
      • ifnewFibernull
        • This indicates that the update is not performed. The key is different.
        • break
      • ifnewFiber** does not reuse **oldFiber, tag deletionoldFiber. —deleteChild()
        • judgeoldFiber && newFiber.alternate === null
      • resettlementnewFiber   
        • lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
      • judgenewFiberWhether it is the first child node
        • is
          • resultingFirstChildThe assignment fornewFiber.
        • no
          • newFiberthroughsiblingJoins the previous child node.reviousNewFiber.sibling = newFiber
      • previousNewFiber = newFiber
      • oldFiber = nextOldFiber
    1. Processing to delete
    • Enter the conditionnewIdx === newChildren.length* * * *.
      • newChildrenAll traversed, not traversedoldChildrenFiberNo longer needed, delete all.
    • Mark deletion residueoldChildrenFiberdeleteChild()
    • return  resultingFirstChild
    1. Process ** added **
    • Enter the conditionoldFiber === null* * * *.
      • oldChildrenFiberAll traversed, not traversednewChildrenAll are newly createdfiber
    • * * cyclenewChildrenJSX* *.
      • generatenewFiber
      • Place thenewFiber. —placeFiber()
      • judgenewFiberWhether it is the first child node
        • is
          • resultingFirstChildThe assignment fornewFiber.
        • no
          • newFiberthroughsiblingJoins the previous child node.reviousNewFiber.sibling = newFiber
      • previousNewFiber = newFiber
    • return  resultingFirstChild
    1. Handling position changes
    • Enter the conditionoldChildrenFibernewChildrenI haven’t even gone through it.
    • The remaining oldChildrenfibergenerateMap(key/index, childFiber)mapRemainingChildren()
      • The priority value of the Map key is fiber.key. If the value is null, the value is fiber.index. Value: fiber.
    • * * cyclenewChildrenJSX* *.
      • generatenewFiberupdateFromMap()
        • throughkey/indexMapTo takeoldFiber
        • If yes, reuse, if no, create.
      • ifMultiplexing fiberTo delete the Map index.
      • resettlementnewFiber
        • lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx)
      • judgenewFiberWhether it is the first child node
        • is
          • resultingFirstChildThe assignment fornewFiber.
        • no
          • newFiberthroughsiblingJoins the previous child node.reviousNewFiber.sibling = newFiber
      • previousNewFiber = newFiber
    1. Delete the Map in theFiber is not reused. —deleteChild()
    1. return  resultingFirstChild
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;// Handle node position changes
  let newIdx = 0;
  let nextOldFiber = null;
  // Loop: only updates are processed
  1. OldChildren/newChildren are done iterating
  // 2. OldFiber is different from newElement(JSX) key (position swap), in the current item break
  // What does update mean? If the key does not change, it is updated. Node type, content,props
  for(; oldFiber ! = =null && newIdx < newChildren.length; newIdx++) {
    // index is the position in JSX
    if (oldFiber.index > newIdx) {
      nextOldFiber = oldFiber;
      oldFiber = null;
    } else {
      nextOldFiber = oldFiber.sibling;
    }
    // Try update fiber
    const newFiber = updateSlot(
      returnFiber,
      oldFiber,
      newChildren[newIdx],
      lanes,
    );
    // Can I update it?
    if (newFiber === null) {
      if (oldFiber === null) {
        oldFiber = nextOldFiber;
      }
      break;
    }
    if (shouldTrackSideEffects) {
      if (oldFiber && newFiber.alternate === null) {
        // Delete oldFiber without multiplexingdeleteChild(returnFiber, oldFiber); }}// Write the fiber flags to be inserted into the Placment
    lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
    if (previousNewFiber === null) {
      / / fiberChildren head
      resultingFirstChild = newFiber;
    } else {
      / / build tree
      previousNewFiber.sibling = newFiber;
    }
    previousNewFiber = newFiber;
    oldFiber = nextOldFiber;
  }

  if (newIdx === newChildren.length) {
    // Delete the remaining old fibers
    deleteRemainingChildren(returnFiber, oldFiber);
    return resultingFirstChild;
  }

  if (oldFiber === null) {
    // oldFiber traversal is complete
    // New fully generated fiber
    for (; newIdx < newChildren.length; newIdx++) {
      const newFiber = createChild(returnFiber, newChildren[newIdx], lanes);
      if (newFiber === null) {
        continue;
      }
      lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
      if (previousNewFiber === null) {
        resultingFirstChild = newFiber;
      } else {
        previousNewFiber.sibling = newFiber;
      }
      previousNewFiber = newFiber;
    }
    return resultingFirstChild;
  }
	/ / generated Map (key/index, fiber)
  const existingChildren = mapRemainingChildren(returnFiber, oldFiber);
	// use Map to reuse
  for (; newIdx < newChildren.length; newIdx++) {
    // reuse/new generation
    const newFiber = updateFromMap(
      existingChildren,
      returnFiber,
      newIdx,
      newChildren[newIdx],
      lanes,
    );
    if(newFiber ! = =null) {
      if (shouldTrackSideEffects) {
        if(newFiber.alternate ! = =null) {
          // Map removes multiplexed fiber
          existingChildren.delete(
            newFiber.key === null? newIdx : newFiber.key, ); }}// Write the fiber flags to be inserted into the Placment
      lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
      if (previousNewFiber === null) {
        resultingFirstChild = newFiber;
      } else{ previousNewFiber.sibling = newFiber; } previousNewFiber = newFiber; }}if (shouldTrackSideEffects) {
    // When Map multiplexing is complete, delete the remaining fiber that cannot be multiplexed
    existingChildren.forEach(child= > deleteChild(returnFiber, child));
  }

  return resultingFirstChild;
}
Copy the code

Correlation function

DeleteChild — Marks the removal of childFiber

deleteChild(returnFiber: Fiber, childToDelete: Fiber)

  • Function:
    • Collect the ones to deletechildFiber. The deletion is performed during the COMMIT phase.
  • logic
    • returnFiber.flagstagChildDeletion
    • returnFiber.deletionsAn array of pushchildFiber
function deleteChild(returnFiber: Fiber, childToDelete: Fiber) :void {
  const deletions = returnFiber.deletions;
  if (deletions === null) {
    returnFiber.deletions = [childToDelete];
    returnFiber.flags |= ChildDeletion;
  } else{ deletions.push(childToDelete); }}Copy the code

UseFiber — Reuse fiber

useFiber(fiber: Fiber, pendingProps: mixed)

  • Function:
    • Multiplexing fiber.
  • logic
    • Clone = Generate new workInProgress, reuse/create new fiber.
      • Multiplexing: Fiber has Alternate
      • New: Fiber without Alternate
    • Reset props, Index, Sibling
      • clone.pendingProps = pendingProps
      • clone.index = 0;
      • clone.sibling = null
    • return clone
function useFiber(fiber: Fiber, pendingProps: mixed) :Fiber {
  const clone = createWorkInProgress(fiber, pendingProps);
  clone.index = 0;
  clone.sibling = null;
  return clone;
}

Copy the code

PlaceChild — Place fiber

placeChild(newFiber: Fiber, lastPlacedIndex: number, newIndex: number )

  • Function:
    • judgenewFiberWhether relocation is neededPalcement
  • variable
    • lastPlacedIndex: The last ** is multiplexed and does not ****Placement**fiber.index
      • The last one has been inserted intodomAnd you don’t have to change positionfiber.index, i.e.,Multiplexing fiber
  • Ideas:
    • newFiberDivided intoMultiplexing fiber, andThe new fiber
    • How does React insert into the DOM?
      • insertOrAppendPlacementNode
      • iffiberOn the right sidesiblingThere areMultiplexing fiber, insertBefore, inserts to the left of it.
      • iffiberOn the right sidesiblingThere are noMultiplexing fiber, appenChild is inserted in turn.
    • ifMultiplexing fiber  in The last multiplexing fiberOn the left, move to the right (placement).
    • ifMultiplexing fiber  Not in The last multiplexing fiberTo the left oflastPlacedIndexoldIndex
      • Due to thefiber.indexValue in for itChildrenJSX arrayindexAnd,lastPlacedIndexWill only assignmentoldFiber.index
      • solastPlacedIndexThe value of must beChildrenJSX arrayValues in the range.
        • lastPlacedIndexThe initial value for the0. Because the array starts at zero0
    • ifThe new fiberDirect placement.
  • logic
    • If the value is mount, return lastPlacedIndex
    • newFiber.index = newIndex; newFiberindexjsxnewChildrenJSXThe index.
    • Determine whetherreuse fiber
      • no
        • Mark Placement
        • return lastPlacedIndex
      • Is,
        • judgeMultiplexing fiberWhether inThe last multiplexing fiberThe left side.
          • oldFiber.index < lastPalcedIndex.
        • On the left side”
          • Mark Placement
          • return lastPlacedIndex    
        • If not >=, modify lastPlacedIndex
          • Return oldIndex. It’s going to be assigned to the last place index
function placeChild(
  newFiber: Fiber,
  lastPlacedIndex: number,
  newIndex: number.) :number {
  newFiber.index = newIndex;
  const current = newFiber.alternate;
  if(current ! = =null) {
    const oldIndex = current.index;
    // If multiplexing fiber, replacment is to the left of the last multiplexing fiber
    if (oldIndex < lastPlacedIndex) {
      newFiber.flags |= Placement;
      return lastPlacedIndex;
    } else {
	    // If multiplexing fiber is not on the left, modify lastPlacedIndex
      returnoldIndex; }}else {
    // New Fiber directly Placment
    newFiber.flags |= Placement;
    returnlastPlacedIndex; }}Copy the code