Interpretation of React's source code and principles (6): reconcileChildren and DIFF algorithm

Written at the beginning of the column (stack armor)

  1. The author is not an expert in front-end technology, but just a novice in front-end technology who likes to learn new things. He wants to learn source code just to cope with the rapidly declining front-end market and the need to find a job. This column is the author's own thinking in the process of learning And experience, there are also many parts that refer to other tutorials. If there are errors or problems, please point them out to the author. The author guarantees that the content is 100% correct. Please do not use this column as a reference answer.

  2. The reading of this column requires you to have a certain foundation of React, JavaScript and front-end engineering. The author will not explain many basic knowledge points, such as: what is babel, what is the syntax of jsx, please refer to it when necessary material.

  3. Many parts of this column refer to a large number of other tutorials. If there are similarities, the author plagiarized them. Therefore, this tutorial is completely open source. You can integrate, summarize and add your own understanding to various tutorials as the author.

Contents of this section

In this section, we will start from the questions left in the previous section and talk about how to finally generate fiber nodes in reconcileChildren() . Among them, we will talk about the DIFF algorithm at the core of React , its core - how to realize multiplexing, and at the same time How did he optimize the time complexity of the comparison

reconcileChildren

We mentioned in the previous section that beginWork at the end we call reconcileChildren this function to process, and reconcileChildrenthis function will call our familiar DIFF algorithm to process our element to generate fiber, so we start with this function, which is in this part of the code Location:

https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberBeginWork.js

This is its main logic. In fact, it is to call different functions according to whether it is the first rendering. We can see that the logic current === nullhere appears again:

export function reconcileChildren(current: Fiber | null, workInProgress: Fiber, nextChildren: any, renderLanes: Lanes) {
    
    
  if (current === null) {
    
    
    workInProgress.child = mountChildFibers(workInProgress, null, nextChildren, renderLanes);
  } else {
    
    
    workInProgress.child = reconcileChildFibers(
      workInProgress,
      current.child,
      nextChildren,
      renderLanes,
    );
  }
}

Let's take a look at the definitions of these two functions. We can see that they are both generated by ChildReconcilerthe function , but the parameters passed in are different.

export const reconcileChildFibers = ChildReconciler(true); 
export const mountChildFibers = ChildReconciler(false); 

Let's look at this ChildReconcilerfunction , its code is very long, let's look at its return value first, it returns areconcileChildFibers

function ChildReconciler(shouldTrackSideEffects) {
    
    
  return reconcileChildFibers;
}

reconcileChildFibersis a function, which is defined inside ChildReconcilerthe function , and its logic is as follows:

  • First judge whether it is a fragment element, if it is, use its child, the Fragment component is a new feature in React 16.2, it can return multiple elements in the render() method without creating additional DOM elements, so we When processing it, you need to ignore this tag
  • Then we process the incoming node. If it is an object, then it is an element element, which is divided into ordinary elements, Lazy type, Portal type, node array, and other five types; if a string or number node is passed in, then Treat as a text node
  • It should be mentioned that if the incoming content does not match any content, it may be boolean, null, undefined, etc., and cannot be converted into a Fiber node
  function reconcileChildFibers(
    returnFiber: Fiber,
    currentFirstChild: Fiber | null,
    newChild: any,
    lanes: Lanes, 
  ): Fiber | null {
    
    
    // 判断是不是 fragment
    const isUnkeyedTopLevelFragment =
      typeof newChild === 'object' &&
      newChild !== null &&
      newChild.type === REACT_FRAGMENT_TYPE &&
      newChild.key === null;
        
    if (isUnkeyedTopLevelFragment) {
    
    
      newChild = newChild.props.children;
    }
    // 判断该节点的类型
    if (typeof newChild === 'object' && newChild !== null) {
    
    
      switch (newChild.$$typeof) {
    
    
        case REACT_ELEMENT_TYPE:
          // 一般的React组件,
          return placeSingleChild(
            reconcileSingleElement(
              returnFiber,
              currentFirstChild,
              newChild,
              lanes,
            ),
          );
        case REACT_PORTAL_TYPE:
          // portal类型 
          return placeSingleChild(
            reconcileSinglePortal(
              returnFiber,
              currentFirstChild,
              newChild,
              lanes,
            ),
          );
        case REACT_LAZY_TYPE:
          // lazy类型 
          const payload = newChild._payload;
          const init = newChild._init;
          return reconcileChildFibers(
            returnFiber,
            currentFirstChild,
            init(payload),
            lanes,
          );
      }
	  // newChild 是一个数组
      if (isArray(newChild)) {
    
    
        return reconcileChildrenArray(
          returnFiber,
          currentFirstChild,
          newChild,
          lanes,
        );
      }
	  // 其他迭代类型,跟数组类似,只是遍历方式不同
      if (getIteratorFn(newChild)) {
    
    
        return reconcileChildrenIterator(
          returnFiber,
          currentFirstChild,
          newChild,
          lanes,
        );
      }
      throwOnInvalidObjectType(returnFiber, newChild);
    }
    // 文本节点
    if (
      (typeof newChild === 'string' && newChild !== '') ||
      typeof newChild === 'number'
    ) {
    
    
      return placeSingleChild(
        reconcileSingleTextNode(
          returnFiber,
          currentFirstChild,
          '' + newChild,
          lanes,
        ),
      );
    }

    if (__DEV__) {
    
    
      if (typeof newChild === 'function') {
    
    
        warnOnFunctionType(returnFiber);
      }
    }

    //说明 newChild 可能是boolean, null, undefined等类型,不能转为fiber节点。直接从删除所有旧的子 Fiber (不继续比较了)
    return deleteRemainingChildren(returnFiber, currentFirstChild);
  }

The function reconcileChildFibers processes the element structure we put in the previous section. It processes the core of this element and compares it with the elements in the first layer, and judges whether the node can be reused according to the rules. We all know that a Fiber node contains A sibing pointer, through the sibing pointer, we can find all the siblings of a node, thus traversing the entire elements of the same layer.

Let's take a look at the comparison process in detail:

reconcileSingleElement

reconcileSingleElementIt is used to process general React components, such as function components, class components, html tags, etc. Let's take a look at its code. His judgment logic is as follows:

  • First extract the key attribute of the current element, and find out whether there is an element with the same key as it in the same layer of the Fiber tree
  • Then we judge whether the element with the same key is the same type as the current element, and if so, reuse the element
  • Create a new node if there is no matching element
function reconcileSingleElement(
  returnFiber: Fiber,
  currentFirstChild: Fiber | null,
  element: ReactElement,
  lanes: Lanes,
): Fiber {
    
    
  //当前节点的key
  const key = element.key;
  let child = currentFirstChild;
  // 循环检测一层中和当前节点的 key
  while (child !== null) {
    
    
    // 找到 key 相等的元素
    if (child.key === key) {
    
    
      const elementType = element.type;
      //元素类型相等
      if (child.key === key) {
    
    
        const elementType = element.type;
         //  REACT_FRAGMENT_TYPE,特判
        if (elementType === REACT_FRAGMENT_TYPE) {
    
    
          if (child.tag === Fragment) {
    
    
            deleteRemainingChildren(returnFiber, child.sibling); // 已找到可复用Fiber子节点且确认只有一个子节点,因此标记删除掉该child节点的所有sibling节点
            const existing = useFiber(child, element.props.children); // 该节点是fragment类型,则复用其children
            existing.return = returnFiber; // 重置新Fiber节点的return指针,指向当前Fiber节点
            //Fragment没有 ref属性
            if (__DEV__) {
    
    
              existing._debugSource = element._source;
              existing._debugOwner = element._owner;
            }
            return existing;
          }
        } else {
    
    
          if (
            child.elementType === elementType ||
            (__DEV__
              ? isCompatibleFamilyForHotReloading(child, element)
              : false) ||
            (typeof elementType === 'object' &&
              elementType !== null &&
              elementType.$$typeof === REACT_LAZY_TYPE &&
              resolveLazy(elementType) === child.type)
          ) {
    
    
            deleteRemainingChildren(returnFiber, child.sibling); // 已找到可复用Fiber子节点且确认只有一个子节点,因此标记删除掉该child节点的所有sibling节点
            const existing = useFiber(child, element.props); // 复用 child 节点和 element.props 属性
            existing.ref = coerceRef(returnFiber, child, element); // 处理ref
            existing.return = returnFiber; // 重置新Fiber节点的return指针,指向当前Fiber节点
            if (__DEV__) {
    
    
              existing._debugSource = element._source;
              existing._debugOwner = element._owner;
            }
            return existing;
          }
        }
      // key一样,类型不同,直接删除该节点和其兄弟节点
      deleteRemainingChildren(returnFiber, child);
      break;
    } else {
    
    
      // 若key不一样,不能复用,标记删除当前单个child节点
      deleteChild(returnFiber, child);
    }
    // 指针指向下一个sibling节点
    child = child.sibling; 
  }
  // 创建一个新的fiber节点
  if (element.type === REACT_FRAGMENT_TYPE) {
    
    
    //  REACT_FRAGMENT_TYPE,特判
    const created = createFiberFromFragment(element.props.children, returnFiber.mode, lanes, element.key);
    created.return = returnFiber; // 新节点的 return 指向到父级节点
    // FRAGMENT 节点没有 ref
    return created;
  } else {
    
    
    // 普通的html元素、函数组件、类组件等
    // 从 element 创建 fiber 节点
    const created = createFiberFromElement(element, returnFiber.mode, lanes);
    created.ref = coerceRef(returnFiber, currentFirstChild, element); // 处理ref
    created.return = returnFiber;
    return created;
  }
}

We noticed that reusing a node calls our useFiber function, let's take a look at this function again:

He called and createWorkInProgresscloned a Fiber node, and put it in our WorkInProgress tree. We mentioned before that there are two trees in our React, and which tree the current pointer in the FiberRootNode node points to, we will display that tree. We put The tree currently being displayed is called current, and the one to be built is called workInProgress, which point to each other through the alternate attribute. Here we use the current tree to create our workInProgress tree:

function useFiber(fiber: Fiber, pendingProps: mixed): Fiber {
    
    
  // 调用了 createWorkInProgress 这个函数来克隆一个节点
  const clone = createWorkInProgress(fiber, pendingProps);
  clone.index = 0;
  clone.sibling = null;
  return clone;
}

export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
    
    
  let workInProgress = current.alternate;
  // workInProgress 是空的,也就是初始化的时候,创建一个新节点
  if (workInProgress === null) {
    
    
    workInProgress = createFiber(
      current.tag,
      pendingProps,
      current.key,
      current.mode,
    );
    workInProgress.elementType = current.elementType;
    workInProgress.type = current.type;
    workInProgress.stateNode = current.stateNode;
    // workInProgress 和 current通过 alternate 属性互相进行指向
    workInProgress.alternate = current;
    current.alternate = workInProgress;
  } else {
    
    
    workInProgress.pendingProps = pendingProps;
    workInProgress.type = current.type;
    workInProgress.flags = NoFlags;
    workInProgress.subtreeFlags = NoFlags;
    workInProgress.deletions = null;

    if (enableProfilerTimer) {
    
    
      workInProgress.actualDuration = 0;
      workInProgress.actualStartTime = -1;
    }
  }

  workInProgress.flags = current.flags & StaticMask;
  workInProgress.childLanes = current.childLanes;
  workInProgress.lanes = current.lanes;

  workInProgress.child = current.child;
  workInProgress.memoizedProps = current.memoizedProps;
  workInProgress.memoizedState = current.memoizedState;
  workInProgress.updateQueue = current.updateQueue;
    
  const currentDependencies = current.dependencies;
  workInProgress.dependencies =
    currentDependencies === null
      ? null
      : {
    
    
          lanes: currentDependencies.lanes,
          firstContext: currentDependencies.firstContext,
        };

  workInProgress.sibling = current.sibling;
  workInProgress.index = current.index;
  workInProgress.ref = current.ref;

  if (enableProfilerTimer) {
    
    
    workInProgress.selfBaseDuration = current.selfBaseDuration;
    workInProgress.treeBaseDuration = current.treeBaseDuration;
  }

  return workInProgress;
}

Finally, we see that we call createFiberFromElementthe function to create a Fiber from element, and this function calls createFiberFromElementthe function for processing. Let's take a look at the logic of this function:

  • It first distinguishes components, html nodes, and other types of components based on the type attribute
  • If it is a component, shouldConstructjudge , because class components must inherit React.Component, and there is an isReactComponent attribute on the prototype of React.Component, and the value is {}, so we can judge
  • Then we call createFiberthe function to create a Fiber node. We used this function in the previous tutorial. Readers who have forgotten it can read the previous tutorial. After obtaining the created Fiber, we assign attributes such as type to it and return
  • This created Fiber will finally be placed in our tree
export function createFiberFromElement(
  element: ReactElement,
  mode: TypeOfMode,
  lanes: Lanes,
): Fiber {
    
    
  let owner = null;
  if (__DEV__) {
    
    
    owner = element._owner;
  }
  const type = element.type;
  const key = element.key;
  const pendingProps = element.props; 
  const fiber = createFiberFromTypeAndProps(
    type,
    key,
    pendingProps,
    owner,
    mode,
    lanes,
  );
  return fiber;
}

export function createFiberFromTypeAndProps(
  type: any, 
  key: null | string,
  pendingProps: any,
  owner: null | Fiber,
  mode: TypeOfMode,
  lanes: Lanes,
): Fiber {
    
    
  let fiberTag = IndeterminateComponent; // 我们还不知道当前fiber是什么类型
  let resolvedType = type;
  if (typeof type === 'function') {
    
    
    // 当前是函数组件或类组件
    if (shouldConstruct(type)) {
    
    
      fiberTag = ClassComponent;
    } else {
    
    
      // 函数组件
    }
  } else if (typeof type === 'string') {
    
    
    // type是普通的html标签
    fiberTag = HostComponent;
  } else {
    
    
    // 其他类型,按下不表
  }
  // 调用 createFiber() 函数,生成 fiber 节点
  const fiber = createFiber(fiberTag, pendingProps, key, mode);
  fiber.elementType = type; // fiber中的 elmentType 与 element 中的 type 一样,
  fiber.type = resolvedType;
  fiber.lanes = lanes;
  return fiber;
}

function shouldConstruct(Component: Function) {
    
    
  // 类组件都是要继承 React.Component 的,而 React.Component 的 prototype 上有一个 isReactComponent 属性,值为{}
  const prototype = Component.prototype;
  return !!(prototype && prototype.isReactComponent);
}

reconcileSingleTextNode

For a pure html text, the processing is relatively simple. Of course, it can also be divided into multi-text arrays and single nodes. Here we will talk about the processing of a single node. The processing of an array type is similar to the processing of multiple ReactElement elements. We will then Will detail:

His processing In reconcileSingleTextNodethis function, we no longer need to determine the key, because the text node has no key attribute; when comparing the data of the current layer, because the text node has only one node and no sibling nodes, only the first one of the current Only when the node is a text node can it be reused, otherwise all elements will be deleted.

// 调度文本节点
function reconcileSingleTextNode(
  returnFiber: Fiber,
  currentFirstChild: Fiber | null,
  textContent: string,
  lanes: Lanes,
): Fiber {
    
    
  // 不再判断文本节点的key,因为文本节点就来没有key
  if (currentFirstChild !== null && currentFirstChild.tag === HostText) {
    
    
    // 若当前节点是文本,则直接删除后续的兄弟节点
    deleteRemainingChildren(returnFiber, currentFirstChild.sibling);
    const existing = useFiber(currentFirstChild, textContent); // 复用这个文本的fiber节点,重新赋值新的文本
    existing.return = returnFiber;
    return existing;
  }
  // 若不存在子节点,或者第一个子节点不是文本节点,直接将当前所有的节点都删除,然后创建出新的文本fiber节点
  deleteRemainingChildren(returnFiber, currentFirstChild);
  const created = createFiberFromText(textContent, returnFiber.mode, lanes);
  created.return = returnFiber;
  return created;
}

reconcileChildrenArray

When the element we need to process is not a single data, but a set of data, such as a div nested with three p tags, this is more common, we need to call reconcileChildrenArray to

It is known that the update of elements in an array may have the following situations:

  • Compared with the new sequence and the old sequence, the elements appear in the same position
  • Added elements to the new sequence
  • The element is removed from the new sequence
  • Elements that appear in both the new sequence and the old sequence, but in a different order

We first traverse the Fiber linked list and our array in order, because our Fiber linked list points to the next node through the sibing pointer, but there is no pointer back to the previous one, so we can only traverse our linked list from front to back, At the same time, we can take a look. In Fiber, we have such an attribute - index, which marks the position of the element in the Fiber sibling list. This attribute will come in handy here:

let resultingFirstChild: Fiber | null = null; // 用于返回的链表
let previousNewFiber: Fiber | null = null; 

let oldFiber = currentFirstChild; // 旧 Fiber 链表的节点,开始指向同一层中的第一个节点
let lastPlacedIndex = 0; // 表示当前已经新建的 Fiber 长度
let newIdx = 0; // 表示遍历 newChildren 的索引指针
let nextOldFiber = null; // 下一个 fiber 节点

for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
    
    
  // 如果旧的节点大于新的
  if (oldFiber.index > newIdx) {
    
    
    nextOldFiber = oldFiber;
    oldFiber = null;
  } else {
    
    
    // 旧 fiber 的索引和n ewChildren 的索引匹配上了,获取 oldFiber 的下一个兄弟节点
    nextOldFiber = oldFiber.sibling;
  }

  // 比较旧的节点和将要转换的 element 
  const newFiber = updateSlot(returnFiber, oldFiber, newChildren[newIdx], lanes);
  // 匹配失败,不能复用
  if (newFiber === null) {
    
    
    if (oldFiber === null) {
    
    
      oldFiber = nextOldFiber;
    }
    break;
  }
  if (shouldTrackSideEffects) {
    
    
    if (oldFiber && newFiber.alternate === null) {
    
    
      // newFiber 不是基于 oldFiber 的 alternate 创建的,销毁旧节点
      deleteChild(returnFiber, oldFiber);
    }
  }
  // 更新lastPlacedIndex
  lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);

  // 更新返回的链表
  if (previousNewFiber === null) {
    
    
    // 若整个链表为空,则头指针指向到newFiber
    resultingFirstChild = newFiber;
  } else {
    
    
    // 若链表不为空,则将newFiber放到链表的后面
    previousNewFiber.sibling = newFiber;
  }
  previousNewFiber = newFiber; 
  oldFiber = nextOldFiber; // 继续下一个节点
}

updateSlotThe function is used to determine whether two elements in the same corresponding position can be reused, and the basis for its determination is whether the keys of the two elements are the same

function updateSlot(returnFiber: Fiber, oldFiber: Fiber | null, newChild: any, lanes: Lanes): Fiber | null {
    
    
  // 若key相等,则更新fiber节点;否则直接返回null
  const key = oldFiber !== null ? oldFiber.key : null;
  if ((typeof newChild === 'string' && newChild !== '') || typeof newChild === 'number') {
    
    
    // 文本节点本身是没有key的,若旧fiber节点有key,则说明无法复用
    if (key !== null) {
    
    
      return null;
    }
    // 若旧fiber没有key,即使他不是文本节点,我们也尝试复用
    return updateTextNode(returnFiber, oldFiber, '' + newChild, lanes);
  }
  if (typeof newChild === 'object' && newChild !== null) {
    
    
    // 若是一些ReactElement类型的,则判断key是否相等;相等则复用;不相等则返回null
    switch (newChild.$$typeof) {
    
    
      case REACT_ELEMENT_TYPE: {
    
    
        if (newChild.key === key) {
    
    
          // key一样才更新
          return updateElement(returnFiber, oldFiber, newChild, lanes);
        } else {
    
    
          // key不一样,则直接返回null
          return null;
        }
      }
      // 省略....
    }
    if (isArray(newChild) || getIteratorFn(newChild)) {
    
    
      // 当前是数组或其他迭代类型,本身是没有key的,若oldFiber有key,则无法复用
      if (key !== null) {
    
    
        return null;
      }
      // 若 newChild 是数组或者迭代类型,则更新为fragment类型
      return updateFragment(returnFiber, oldFiber, newChild, lanes, null);
    }
  }
  // 其他类型不进行处理,直接返回null
  return null;
}

If the old linked list has not been traversed after the loop is over, it means that the remaining nodes are no longer needed and can be deleted directly

// 遍历结束(访问相同数量的元素了)
if (newIdx === newChildren.length) {
    
    
  // 删除旧链表中剩余的节点
  deleteRemainingChildren(returnFiber, oldFiber);
  // 返回新链表的头节点指针
  return resultingFirstChild;
}

If after the above operations, the old Fiber is used up, but the element elements are not all accessed, it means that the remaining elements do not have corresponding nodes that can be reused. Just create a new node directly. The logic is basically the same as that createChildofupdateSlot Questions used:

// 若旧数据中所有的节点都复用了
if (oldFiber === null) {
    
    
  //创建新元素
  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;
}

If the new and old elements have not been traversed, it means that the elements are out of order. We need to put the old nodes in the Map, and then get them according to the key or index.

//如果新旧元素都没遍历完, mapRemainingChildren 生成一个以 oldFiber 的 key 为 key, oldFiber 为 value 的 map
const existingChildren = mapRemainingChildren(returnFiber, oldFiber);

for (; newIdx < newChildren.length; newIdx++) {
    
    
  // 从 map 中查找是否存在可以复用的fiber节点,然后生成新的fiber节点
  const newFiber = updateFromMap(existingChildren, returnFiber, newIdx, newChildren[newIdx], lanes);
  if (newFiber !== null) {
    
    
    if (shouldTrackSideEffects) {
    
    
      if (newFiber.alternate !== null) {
    
    
         //newFiber.alternate指向到current,若current不为空,说明复用了该fiber节点,这里我们要在 map 中删除,因为后面会把 map 中剩余未复用的节点删除掉的
        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) {
    
    
  // 将 map 中没有复用的 fiber 节点添加到删除队列中,等待删除
  existingChildren.forEach(child => deleteChild(returnFiber, child));
}
// 返回新链表的头节点指针
return resultingFirstChild;

diff algorithm

Ok, we have just read the operation reconcileChildren done , now let's sort out its processing logic and abstract it into an algorithm for updating our Fiber tree. This algorithm is our React's DIFF algorithm. Let's first talk about the major premise of React's DIFF:

  • In web development, there are very few cross-level element changes. For example, now I have a p tag. What I often do is: change the content of this tag, or insert another p tag at the same level as the p tag. We rarely nest a div around the p tag when updating the page.
  • Two different types of components will produce two different tree structures
  • The key in React is unique, a key can uniquely identify an element

Based on this premise, the strategy given by React is as follows:

React only performs hierarchical comparison of the virtual DOM tree, and does not consider the cross-level comparison of nodes. In this way, the entire comparison can be completed only by traversing the virtual Dom tree once. When comparing, select the children of the same parent node for comparison every time, as shown in the figure below, all children under the same parent node will be compared

Please add a picture description

If there is a cross-layer movement, then when the parent node is compared, the cross-layer moved node will be deleted. For example, in the figure below, when comparing the root, A has been marked as deleted, and then B When comparing, although component A appears, we will not reuse it, but will create a new component A

insert image description here

For different types of components, React does not need to perform a comparison operation by default, and directly recreates them. For components of the same type, use the diff strategy for comparison, as shown in the figure below: the root nodes of the two components are different, that is to say, they are not a component, but the content of the components is the same. In this case, React will not reuse, but Is to create a new one directly:

insert image description here

For nodes at the same layer, use the unique key to determine whether the same node exists in the old collection, if not, create it; if so, determine whether it needs to be moved. The whole operation is divided into three types: delete, add and move . As shown in the figure below: we have moved A, deleted C, and added E.

insert image description here
insert image description here

After the optimization of the above logic, React optimizes the O( n^3 ) of the classic DIFF algorithm (violent recursive comparison) to almost O( n ).

Now you can review the source code we just explained. Now you should have a new understanding of the source code, and some of the problems that most people may not be clear when reading the source code for the first time have also been resolved:

  • Why when processing a node, if it cannot be parsed (null, undefined), all elements should be deleted directly: because it can no longer be parsed, it means that it does not need the reuse provided by the old node, so the only old element The ones on the same layer have no value, just delete them all.

  • Why do you delete the remaining nodes when you find a node that can be reused when processing a node: because if you are processing a single node, then in the same layer, it only needs one node to be reused. If you can find a reused node, The remaining nodes are not needed, just delete them directly

  • Why do you find an element with the same key but a different type, and delete all the elements directly, because the key is unique, if the key is the same but the type is different, it means a different component, then calling two Generate two different tree structure principles, just delete them directly

  • What needs to be added here is that when we deal with array type elements, the comparison method we use is to reuse nodes first, and then deal with deletion, addition and movement, because in the actual React development process, reuse nodes, The frequency of adding and deleting is much higher than that of moving, and we handle the logic of moving last

Summarize

In this section, we finally talked about the processing of the reconcileChildren() function, which uses the DIFF algorithm to determine which nodes can be reused . The core is:

  • Only compare elements at the same level
  • If the root node is different, the whole tree is considered to be different
  • Use a unique key to identify elements and find out whether they can be reused

In this regard, for a single node, we compare whether there are reusable nodes in the same layer ; for a group of nodes, we compare in the same layer to determine whether each element should be reused, deleted, added or moved .

After the judgment is completed, we create a Fiber node for each React element node, which either calls to createWorkInProgressclone a Fiber, or creates a new Fiber, and the newly generated Fiber will be placed WorkInProgressin the tree, and we put the currently displayed tree It is called current, the one to be built is called workInProgress, and it points to each other through the alternate attribute.

Then in the following chapters, we only have the last step to deal with, that is, we need to synchronize our update of the virtual DOM to our real DOM and show it to the user, then a React rendering is completed , this stage is called the Commit stage, and we will talk about it in detail in the next section.

Guess you like

Origin blog.csdn.net/weixin_46463785/article/details/130085962