【学习笔记】React18源码学习(二)Reconciler

140 阅读7分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 2 天,点击查看活动详情

【学习笔记】React18源码学习(一) 设计理念与Scheduler

0. 工作周期

renderRootSync

设置渲染的上下文,为工作准备一个新的堆栈,然后进入一个循环,在循环中通过重复调用workLoopSync函数来处理工作。如果在此循环中抛出错误,则会被handleThrow函数捕获和处理。

 outer: do {
    try {
      if (
        workInProgressSuspendedReason !== NotSuspended &&
        workInProgress !== null
      ) {
       // 工作循环被挂起。在同步渲染期间,我们不会让出主线程。
        const unitOfWork = workInProgress;
        const thrownValue = workInProgressThrownValue;
        switch (workInProgressSuspendedReason) {
          case SuspendedOnHydration: {
            // 一个更新流入了一个未注水的树。
            // 中断当前渲染,以便workLoop可以切换到注水的模式。
            resetWorkInProgressStack();
            workInProgressRootExitStatus = RootDidNotComplete;
            break outer;
          }
          default: 
            workInProgressSuspendedReason = NotSuspended;
            workInProgressThrownValue = null;
            unwindSuspendedUnitOfWork(unitOfWork, thrownValue);
            break;
          }
        }
      }
      // 开始Work Loop
      workLoopSync();
      break;
    } catch (thrownValue) {
      handleThrow(root, thrownValue);
    }
  } while (true);

workLoopSync

function workLoopSync() {
  // Perform work without checking if we need to yield between fiber.
  while (workInProgress !== null) {
    performUnitOfWork(workInProgress);
  }
}

如果存在WorkInProgress 就执行具体工作,

performUnitOfWork

在Fiber树中每一个单独的节点都可以当作一个工作单元,performUnitOfWork 用于执行工作单元,并按照优先级顺序执行这些工作单元,从而实现协调渲染。

performUnitOfWork() 函数的主要作用是处理当前工作单元,并返回下一个工作单元。其实现通常涉及以下步骤:

  1. 处理当前工作单元的任务,例如调用组件的生命周期方法、处理事件等。
  2. 检查是否需要中断当前工作单元的执行(例如,在有更高优先级的工作单元需要执行时)。
  3. 如果当前工作单元有子节点,则将第一个子节点作为下一个工作单元返回。
  4. 否则,如果当前工作单元有兄弟节点,则将下一个兄弟节点作为下一个工作单元返回。
  5. 否则,回溯到父节点,查找有兄弟节点的祖先节点,并将下一个兄弟节点作为下一个工作单元返回。
  6. 如果没有找到下一个工作单元,则表示整个工作单元链表执行完成。

需要注意的是,performUnitOfWork() 函数在执行期间可能会被多次调用,因为中断和恢复工作单元的执行是 React Fiber 的核心机制之一。在每次中断和恢复执行时,performUnitOfWork() 函数都会被调用,并根据需要返回下一个工作单元。

function performUnitOfWork(unitOfWork: Fiber): void {
  const current = unitOfWork.alternate;
  setCurrentDebugFiberInDEV(unitOfWork);

  let next;
  if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
    startProfilerTimer(unitOfWork);
    next = beginWork(current, unitOfWork, renderLanes);
    stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
  } else {
    // 这里开始深度优先遍历
    next = beginWork(current, unitOfWork, renderLanes);
  }

  resetCurrentDebugFiberInDEV();
  unitOfWork.memoizedProps = unitOfWork.pendingProps;
  if (next === null) {
    // If this doesn't spawn new work, complete the current work.
    // 
    completeUnitOfWork(unitOfWork);
  } else {
    // 这里的全局变量
    workInProgress = next;
    // 因 `renderRootSync`里的循环有个判断 workInProgress !== null
    // 所以会一直调用beginWork,直到当前节点没有子节点
  }

  ReactCurrentOwner.current = null;
}

首屏渲染的示例代码

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
      </header>
    </div>
  );
}

套到上面的代码里逻辑如下

  1. root节点进入beginWork
  2. function Component App()进入beginWork
  3. div 节点进入beginWork
  4. header 节点进入beginWork
  5. img节点进入beginWork
  6. img没有子节点 进入 completeWork
  7. img 兄弟节点 p进入beginWork
  8. 文件节点Edit 进入 beginWork
  9. code进入beginWork
  10. code进入 completeWork,ps 当子节点只有一个文本节点时会直接进入completework
  11. and save to reload. 进入 beginWork
  12. and save to reload. 进入 completeWork 下面就用上面的步骤看下他们是怎么串起来的

1. root节点进入beginWork`

1.1 beginWork

// 如果 `didReceiveUpdate` 被标记为 true,那么组件将会更新;否则,组件将不会更新。
didReceiveUpdate = false;
// 由于root节点的tag是3
switch (workInProgress.tag) {
    case HostRoot:
      return updateHostRoot(current, workInProgress, renderLanes);
}

1.2 updateHostRoot

function updateHostRoot(current, workInProgress, renderLanes) {
  // 上下文
  pushHostRootContext(workInProgress);

  var nextProps = workInProgress.pendingProps;
  var prevState = workInProgress.memoizedState;
  var prevChildren = prevState.element;
  cloneUpdateQueue(current, workInProgress);
  processUpdateQueue(workInProgress, nextProps, null, renderLanes);
  var nextState = workInProgress.memoizedState;
  var root = workInProgress.stateNode;
  var nextChildren = nextState.element;
  // 生成子节点的fiber
  reconcileChildren(current, workInProgress, nextChildren, renderLanes);
}

1.3 reconcileChildren

function reconcileChildren(current, workInProgress, nextChildren, renderLanes) {
  if (current === null) {
    workInProgress.child = mountChildFibers(workInProgress, null, nextChildren, renderLanes);
  } else {
    // 当前是root 节点 current !==null
    workInProgress.child = reconcileChildFibers(workInProgress, current.child, nextChildren, renderLanes);
  }
}

1.4 reconcileChildFibers

// returnFiber 当前root fiber 节点
// newChild = {type:f App(),$$typeof: Symbol(react.element),[[Prototype]]:Object}
function reconcileChildFibers(returnFiber, currentFirstChild, newChild, lanes) {
    if (typeof newChild === 'object' && newChild !== null) {
      switch (newChild.$$typeof) {
        case REACT_ELEMENT_TYPE:
            // 进入的这里
          return placeSingleChild(reconcileSingleElement(returnFiber, currentFirstChild, newChild, lanes));
       
      }
}

1.5 reconcileSingleElement

// element 为 f app() 节点
function reconcileSingleElement(returnFiber, currentFirstChild, element, lanes) {
    if (element.type === REACT_FRAGMENT_TYPE) {
      var created = createFiberFromFragment(element.props.children, returnFiber.mode, lanes, element.key);
      created.return = returnFiber;
      return created;
    } else {
      var _created4 = createFiberFromElement(element, returnFiber.mode, lanes);

      _created4.ref = coerceRef(returnFiber, currentFirstChild, element);
      _created4.return = returnFiber;
      return _created4;
    }
}

1.6 createFiberFromElement

function createFiberFromElement(element, mode, lanes) {
    const fiber = createFiberFromTypeAndProps(
        type,
        key,
        pendingProps,
        owner,
        mode,
        lanes,
      );
    return fiber;
}

最终返回了一个子节点的fiber

2. 当 header 节点进入beginWork

2.1 beginWork

由于header是HostComponent 所以进入 updateHostComponent,当前的current是null,renderLanes 为32

case HostComponent:
      return updateHostComponent(current, workInProgress, renderLanes);

2.2 updateHostComponent

这里做了判断当子节点只有一个child并且是纯文本节点,那么nextChildren 会设为null,这样子节点就会直接进入completeWork

function updateHostComponent(current, workInProgress, renderLanes) {
   // 判断
  const isDirectTextChild = shouldSetTextContent(type, nextProps);
  if (isDirectTextChild) {
    
    nextChildren = null;
  } else if (prevProps !== null && shouldSetTextContent(type, prevProps)) {
    workInProgress.flags |= ContentReset;
  }
  // 生成Children
  reconcileChildren(current, workInProgress, nextChildren, renderLanes);
  return workInProgress.child;
}

这里的reconcileChildren跟上面1.3的一样

2.3 ReconcileChildFibers

image.png

// newChild 是个数组
function reconcileChildFibers(returnFiber, currentFirstChild, newChild, lanes) {
    if (isArray(newChild)) {
        return reconcileChildrenArray(returnFiber, currentFirstChild, newChild, lanes);
      }
}

2.4 reconcileChildrenArray

function reconcileChildrenArray(
    returnFiber: Fiber,
    currentFirstChild: Fiber | null,
    newChildren: Array<any>,
    lanes: Lanes,
  ): Fiber | null {
  if (oldFiber === null) {
      for (; newIdx < newChildren.length; newIdx++) {
        // 从这里进入创建新fiber的逻辑
        const newFiber = createChild(returnFiber, newChildren[newIdx], lanes);
        if (newFiber === null) {
          continue;
        }
        // 插入fiber树
        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;
    }
    // 把第一个子节点return出去,进入新一轮的workloop
    return resultingFirstChild;
  }

2.5 createChild

createChild同1.6里的 createFiberFromElement

3. 当 code 节点进入 beginWork

3.1 updateHostComponent

前面的过程不再赘述,从 updateHostComponent 这个方法里,有个const isDirectTextChild = shouldSetTextContent(type, nextProps);,因为code只有一个字节点并且是纯文本,所以为true。然后next会被设为null,进入performUnitOfWork

// 简化一下
let next;
// 综上所述当unitOfWork是code时 返回null
next = beginWork(current, unitOfWork, renderLanes);
if (next === null) {
    // 进入complete
    completeUnitOfWork(unitOfWork);
} else {
    workInProgress = next;
}

3.2 completeUnitOfWork

大致工作跟performUnitOfWork 一致 参照0.3章节

function completeUnitOfWork(unitOfWork) {
    do {
      // ...省略前面的赋值
      next = completeWork(current, completedWork, renderLanes);
      if (next !== null) {
        // Completing this fiber spawned new work. Work on that next.
        workInProgress = next;
        return;
      } else {
      // ...省略后面的跟当前code不进入的逻辑
      }
    } while (completedWork !== null)
}

3.3 completeWork

工作流程如下:

  1. 首先,检查当前 fiber 节点的 tag 类型,判断该节点是否为 DOM 元素、文本节点、类组件、函数组件、HostPortal 或者 Suspense 等类型。
  2. 然后,对于不同的节点类型,执行相应的操作,例如更新 DOM 元素的属性、将文本内容插入到父节点中、调用类组件的 componentWillUnmount 或者 useEffect 等副作用钩子函数。
  3. 接下来,如果当前节点有兄弟节点,则将兄弟节点添加到父节点的 child 属性中。
  4. 如果当前节点没有兄弟节点,则遍历当前节点的父节点的祖先节点,找到下一个有兄弟节点的节点,并将其添加到该节点的 child 属性中。如果遍历到了根节点仍然没有找到有兄弟节点的节点,则表明整个树已经遍历完成。
  5. 最后,如果当前节点是类组件或函数组件,则更新它的 effect list 和 effect tag,以便在 commit 阶段执行相关的副作用操作。
function completeWork(current,workInProgress,lanes) {
case HostComponent: {
      popHostContext(workInProgress);
      const type = workInProgress.type;
      if (current !== null && workInProgress.stateNode != null) {
        // 省略
      } else {
        const currentHostContext = getHostContext();
        const wasHydrated = popHydrationState(workInProgress);
        if (wasHydrated) {
          // 注水 ssr 相关
        } else {
          const rootContainerInstance = getRootHostContainer();
          // 创建DOM元素
          const instance = createInstance(
            type,
            newProps,
            rootContainerInstance,
            currentHostContext,
            workInProgress,
          );
          // 挂载所有子节点
          appendAllChildren(instance, workInProgress, false, false);
          // 将实例存储在stateNode中
          workInProgress.stateNode = instance;
          
          // 某些渲染器需要在提交阶段应用效果以进行初始挂载。
          // (例如,DOM渲染器支持对某些元素进行自动聚焦)。
          // 确保这样的渲染器被安排在稍后进行工作。
          if (
            // 设置DOM属性
            finalizeInitialChildren(
              instance,
              type,
              newProps,
              currentHostContext,
            )
          ) {
            markUpdate(workInProgress);
          }
        }

        if (workInProgress.ref !== null) {
          // If there is a ref on a host node we need to schedule a callback
          markRef(workInProgress);
        }
      }
      
      bubbleProperties(workInProgress);
      return null;
    }
}

3.4 createInstance

创建dom元素

function createInstance(
  type: string,
  props: Props,
  rootContainerInstance: Container,
  hostContext: HostContext,
  internalInstanceHandle: Object,
):Instance {
  // 通过createElement 方法创建DOM元素
  const domElement: Instance = createElement(
    type,
    props,
    rootContainerInstance,
    parentNamespace,
  );
  // 缓存
  precacheFiberNode(internalInstanceHandle, domElement);
  updateFiberProps(domElement, props);
  return domElement;
}

3.5 finalizeInitialChildren

设置属性

function finalizeInitialChildren(
  domElement: Instance,
  type: string,
  props: Props,
  hostContext: HostContext,
): boolean {
  setInitialProperties(domElement, type, props);
  switch (type) {
    case 'button':
    case 'input':
    case 'select':
    case 'textarea':
      return !!props.autoFocus;
    case 'img':
      return true;
    default:
      return false;
  }
}

3.6 setInitialProperties

function setInitialProperties(
  domElement: Element,
  tag: string,
  rawProps: Object,
): void {
// 一推case 放个input的例子
    case 'input':
    ReactDOMInputInitWrapperState(domElement, rawProps);
    props = ReactDOMInputGetHostProps(domElement, rawProps);
    // 我们监听这个事件,以确保模拟冒泡的监听器 
    // 仍然会为无效事件触发。
    listenToNonDelegatedEvent('invalid', domElement);
    break;
}