react 历史版本

  • 在 react 15 版本,出现了批处理,即多个 setState,会被统一更新,但是,虽然优化了 setState,但是如果 setState 太多,影响的 dom 树庞大,会让主线程长时间占用,造成浏览器卡顿
  • 在 react 16 中,出现了 React Fiber 的概念,使用 requestIdleCallback(低优先级任务),来不断的交还主线程控制权,从而实现分片任务调度,避免卡帧。内部也对每个更新实现了一个优先级的绑定,可以中断任务,来调度任务的优先级。
  • 在 react 17 中,扩展了 fiber 优先级,从指定一个优先级任务,优化到指定一个连续的优先级区间

什么是 Fiber

fiber 是为了解决 react 卡顿出现的一种概念,利用 requestIdleCallback 来不断的交还主线程控制权,达到更新调度过程的切片化。内部并没有使用 requestIdleCallback,而是利用宏任务来实现 Fiber,这里可能会误导人。

fiber 结构

function FiberNode(
  tag: WorkTag,
  pendingProps: mixed,
  key: null | string,
  mode: TypeOfMode,
) {
  // Instance
  this.tag = tag; // fiber类型,function | class | host
  this.key = key; // key
  this.elementType = null; // 节点类型
  this.type = null; //更具体的类型,比如hostComponent,对应tagName
  this.stateNode = null; //真实dom节点

  // Fiber
  this.return = null; // parent
  this.child = null; // 子节点
  this.sibling = null; // 兄弟节点
  this.index = 0; // 位置

  this.ref = null; //Ref
// 更新造成的状态变更
  this.pendingProps = pendingProps;
  this.memoizedProps = null;
  this.updateQueue = null;
  this.memoizedState = null;
  this.dependencies = null;

  this.mode = mode;

  // Effects
  this.flags = NoFlags; // 标价类型,比如delete
  this.subtreeFlags = NoFlags;
  this.deletions = null;

  this.lanes = NoLanes;  //相当于react16的expericeTime
  this.childLanes = NoLanes; // 子节点优先级

  this.alternate = null; // 只想workInProgress,对比变更

  if (enableProfilerTimer) {
    // Note: The following is done to avoid a v8 performance cliff.
    //
    // Initializing the fields below to smis and later updating them with
    // double values will cause Fibers to end up having separate shapes.
    // This behavior/bug has something to do with Object.preventExtension().
    // Fortunately this only impacts DEV builds.
    // Unfortunately it makes React unusably slow for some applications.
    // To work around this, initialize the fields below with doubles.
    //
    // Learn more about this here:
    // https://github.com/facebook/react/issues/14365
    // https://bugs.chromium.org/p/v8/issues/detail?id=8538
    this.actualDuration = Number.NaN;
    this.actualStartTime = Number.NaN;
    this.selfBaseDuration = Number.NaN;
    this.treeBaseDuration = Number.NaN;

    // It's okay to replace the initial doubles with smis after initialization.
    // This won't trigger the performance cliff mentioned above,
    // and it simplifies other profiler code (including DevTools).
    this.actualDuration = 0;
    this.actualStartTime = -1;
    this.selfBaseDuration = 0;
    this.treeBaseDuration = 0;
  }

  if (__DEV__) {
    // This isn't directly used but is handy for debugging internals:
    this._debugID = debugCounter++;
    this._debugSource = null;
    this._debugOwner = null;
    this._debugNeedsRemount = false;
    this._debugHookTypes = null;
    if (!hasBadMapPolyfill && typeof Object.preventExtensions === 'function') {
      Object.preventExtensions(this);
    }
  }
}

ReactDOM.render()

直接上俺自己画的神图。。。。。。
img

react 的三个阶段

  • scheduler 调度过程,划分任务等级及类型、生成 Fiber 等
  • reconciler 上面图中的 workLoop,Dom Diff、打上 flags(react 16 即上 effectTag)、生成 update 等
  • commit 执行阶段,将 fiber 渲染到页面,禁止打断的过程

scheduler

可以结合着上面的图看

  • 1.从 render 入口我们一步步看,render 执行 legacyRenderSubtreeIntoContainer 函数,并传入入口 component,绑定 Dom,以及 callback
export function render(
  element: React$Element<any>,
  container: Container,
  callback: ?Function,
) {
  invariant(
    isValidContainer(container),
    'Target container is not a DOM element.',
  );
  if (__DEV__) {
    const isModernRoot =
      isContainerMarkedAsRoot(container) &&
      container._reactRootContainer === undefined;
    if (isModernRoot) {
      console.error(
        'You are calling ReactDOM.render() on a container that was previously ' +
          'passed to ReactDOM.createRoot(). This is not supported. ' +
          'Did you mean to call root.render(element)?',
      );
    }
  }
  return legacyRenderSubtreeIntoContainer(
    null,
    element,
    container,
    false,
    callback,
  );
}
  • 2.如果是 root,不走批处理,并执行 render 的回调函数
function legacyRenderSubtreeIntoContainer(
  parentComponent: ?React$Component<any, any>,
  children: ReactNodeList,
  container: Container,
  forceHydrate: boolean,
  callback: ?Function,
) {

  let root: RootType = (container._reactRootContainer: any);
  let fiberRoot;
  // ...
  if (!root) {
    // ...
    if (typeof callback === 'function') {
      const originalCallback = callback;
      callback = function() {
        const instance = getPublicRootInstance(fiberRoot);
        originalCallback.call(instance);
      };
    }
    // Initial mount should not be batched.
    unbatchedUpdates(() => {
      updateContainer(children, fiberRoot, parentComponent, callback);
    });
  } else {
    fiberRoot = root._internalRoot;
    if (typeof callback === 'function') {
      const originalCallback = callback;
      callback = function() {
        const instance = getPublicRootInstance(fiberRoot);
        originalCallback.call(instance);
      };
    }


    // Update
    updateContainer(children, fiberRoot, parentComponent, callback);
  }
  return getPublicRootInstance(fiberRoot);
}
  • 3.通过 lane 指定任务优先级,即 react 16 的 expericeTime,创建 update,最后执行图中的 scheduleUpdateOnFiber
export function createContainer(
  containerInfo: Container,
  tag: RootTag,
  hydrate: boolean,
  hydrationCallbacks: null | SuspenseHydrationCallbacks,
  strictModeLevelOverride: null | number,
): OpaqueRoot {
  return createFiberRoot(
    containerInfo,
    tag,
    hydrate,
    hydrationCallbacks,
    strictModeLevelOverride,
  );
}

export function updateContainer(
  element: ReactNodeList,
  container: OpaqueRoot,
  parentComponent: ?React$Component<any, any>,
  callback: ?Function,
): Lane {
  if (__DEV__) {
    onScheduleRoot(container, element);
  }
  const current = container.current;
  const eventTime = requestEventTime();
  // ...dev
  const lane = requestUpdateLane(current);

  if (enableSchedulingProfiler) {
    markRenderScheduled(lane);
  }

  const context = getContextForSubtree(parentComponent);
  if (container.context === null) {
    container.context = context;
  } else {
    container.pendingContext = context;
  }

  // ...

  const update = createUpdate(eventTime, lane);
  update.payload = {element};
  callback = callback === undefined ? null : callback;
  if (callback !== null) {
    if (__DEV__) {
      if (typeof callback !== 'function') {
        console.error(
          'render(...): Expected the last optional `callback` argument to be a ' +
            'function. Instead received: %s.',
          callback,
        );
      }
    }
    update.callback = callback;
  }

  enqueueUpdate(current, update, lane);
  const root = scheduleUpdateOnFiber(current, lane, eventTime);
  if (root !== null) {
    entangleTransitions(root, current, lane);
  }

  return lane;
}

我们看看 lane 是什么

    export const NoLanes: Lanes = /*                        */ 0b0000000000000000000000000000000;
export const NoLane: Lane = /*                          */ 0b0000000000000000000000000000000;

export const SyncLane: Lane = /*                        */ 0b0000000000000000000000000000001;
export const SyncBatchedLane: Lane = /*                 */ 0b0000000000000000000000000000010;

export const InputDiscreteHydrationLane: Lane = /*      */ 0b0000000000000000000000000000100;
export const InputDiscreteLane: Lanes = /*              */ 0b0000000000000000000000000001000;

const InputContinuousHydrationLane: Lane = /*           */ 0b0000000000000000000000000010000;
export const InputContinuousLane: Lanes = /*            */ 0b0000000000000000000000000100000;

export const DefaultHydrationLane: Lane = /*            */ 0b0000000000000000000000001000000;
export const DefaultLane: Lanes = /*                    */ 0b0000000000000000000000010000000;

可以看到,lane 相比于之前版本的 expericeTime,通过二进制位并运算来指定任务优先级

const DefaultLane: Lanes = /*                    */ 0b0000000000000000000000010000000;
const lane &=0b0000000000000000000000010000000;
lane &= DefaultLane;
  • 4.判断任务的 lane,如果是同步任务,先加到 syncQueue 中,暂不执行 performSyncWorkOnRoot,否则,执行 ensureRootIsScheduled,通过 schedulePendingInteractions 来记录打断次数
export function scheduleUpdateOnFiber(
  fiber: Fiber,
  lane: Lane,
  eventTime: number,
): FiberRoot | null {
  checkForNestedUpdates();
  warnAboutRenderPhaseUpdatesInDEV(fiber);

  const root = markUpdateLaneFromFiberToRoot(fiber, lane);
  if (root === null) {
    warnAboutUpdateOnUnmountedFiberInDEV(fiber);
    return null;
  }

  // Mark that the root has a pending update.
  markRootUpdated(root, lane, eventTime);

  if (lane === SyncLane) {
    if (
      // Check if we're inside unbatchedUpdates
      (executionContext & LegacyUnbatchedContext) !== NoContext &&
      // Check if we're not already rendering
      (executionContext & (RenderContext | CommitContext)) === NoContext
    ) {
      // Register pending interactions on the root to avoid losing traced interaction data.
      schedulePendingInteractions(root, lane);

      performSyncWorkOnRoot(root);
    } else {
      ensureRootIsScheduled(root, eventTime);
      schedulePendingInteractions(root, lane);
      if (
        executionContext === NoContext &&
        (fiber.mode & ConcurrentMode) === NoMode
      ) {
        resetRenderTimer();
        flushSyncCallbackQueue();
      }
    }
  } else {
    // Schedule other updates after in case the callback is sync.
    ensureRootIsScheduled(root, eventTime);
    schedulePendingInteractions(root, lane);
  }

  return root;
}
  • 5.ensureRootIsScheduled 内,构建 scheduleCallback。
function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
    // 此处省略。。。。
    let newCallbackNode;
  if (newCallbackPriority === SyncLanePriority) {
    // Special case: Sync React callbacks are scheduled on a special
    // internal queue
    scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
    newCallbackNode = null;
  } else if (newCallbackPriority === SyncBatchedLanePriority) {
    newCallbackNode = scheduleCallback(
      ImmediateSchedulerPriority,
      performSyncWorkOnRoot.bind(null, root),
    );
  } else {
    const schedulerPriorityLevel = lanePriorityToSchedulerPriority(
      newCallbackPriority,
    );
    newCallbackNode = scheduleCallback(
      schedulerPriorityLevel,
      performConcurrentWorkOnRoot.bind(null, root),
    );
  }
}
  • 6.scheduleCallback 中执行 unstable_scheduleCallback,这里就是异步任务执行的核心逻辑,判断 taskQueue 中是否有任务,异步是否过期,添加到 taskQueue 中,否则,更新 timerQueue 中的过期时间
// scheduleCallback
export function scheduleCallback(
  reactPriorityLevel: ReactPriorityLevel,
  callback: SchedulerCallback,
  options: SchedulerCallbackOptions | void | null,
) {
  const priorityLevel = reactPriorityToSchedulerPriority(reactPriorityLevel);
  return Scheduler_scheduleCallback(priorityLevel, callback, options);
}
// unstable_scheduleCallback
function unstable_scheduleCallback(priorityLevel, callback, options) {
  var currentTime = getCurrentTime();

  var startTime;
  if (typeof options === 'object' && options !== null) {
    var delay = options.delay;
    if (typeof delay === 'number' && delay > 0) {
      startTime = currentTime + delay;
    } else {
      startTime = currentTime;
    }
  } else {
    startTime = currentTime;
  }

  var timeout;
  switch (priorityLevel) {
    case ImmediatePriority:
      timeout = IMMEDIATE_PRIORITY_TIMEOUT;
      break;
    case UserBlockingPriority:
      timeout = USER_BLOCKING_PRIORITY_TIMEOUT;
      break;
    case IdlePriority:
      timeout = IDLE_PRIORITY_TIMEOUT;
      break;
    case LowPriority:
      timeout = LOW_PRIORITY_TIMEOUT;
      break;
    case NormalPriority:
    default:
      timeout = NORMAL_PRIORITY_TIMEOUT;
      break;
  }

  var expirationTime = startTime + timeout;

  var newTask = {
    id: taskIdCounter++,
    callback,
    priorityLevel,
    startTime,
    expirationTime,
    sortIndex: -1,
  };
  if (enableProfiling) {
    newTask.isQueued = false;
  }

  if (startTime > currentTime) {
    // This is a delayed task.
    newTask.sortIndex = startTime;
    push(timerQueue, newTask);
    if (peek(taskQueue) === null && newTask === peek(timerQueue)) {
      // All tasks are delayed, and this is the task with the earliest delay.
      if (isHostTimeoutScheduled) {
        // Cancel an existing timeout.
        cancelHostTimeout();
      } else {
        isHostTimeoutScheduled = true;
      }
      // Schedule a timeout.
      requestHostTimeout(handleTimeout, startTime - currentTime);
    }
  } else {
    newTask.sortIndex = expirationTime;
    push(taskQueue, newTask);
    if (enableProfiling) {
      markTaskStart(newTask, currentTime);
      newTask.isQueued = true;
    }
    // Schedule a host callback, if needed. If we're already performing work,
    // wait until the next time we yield.
    if (!isHostCallbackScheduled && !isPerformingWork) {
      isHostCallbackScheduled = true;
      requestHostCallback(flushWork);
    }
  }

  return newTask;
}
  • 7.如果任务过期或者 taskQueue 中没有任务,则执行 requestHostCallback,可以,模拟 window.requestIdleCallback 就是在这里,先判断 setImmediate 是否存在,不存在即使用了 MessageChannel,两者皆属于宏任务
// requestHostCallback
function requestHostCallback(callback) {
  scheduledHostCallback = callback;
  if (!isMessageLoopRunning) {
    isMessageLoopRunning = true;
    schedulePerformWorkUntilDeadline();
  }
}
//schedulePerformWorkUntilDeadline
let schedulePerformWorkUntilDeadline;
if (typeof setImmediate === 'function') {
  // Node.js and old IE.
  // There's a few reasons for why we prefer setImmediate.
  //
  // Unlike MessageChannel, it doesn't prevent a Node.js process from exiting.
  // (Even though this is a DOM fork of the Scheduler, you could get here
  // with a mix of Node.js 15+, which has a MessageChannel, and jsdom.)
  // https://github.com/facebook/react/issues/20756
  //
  // But also, it runs earlier which is the semantic we want.
  // If other browsers ever implement it, it's better to use it.
  // Although both of these would be inferior to native scheduling.
  schedulePerformWorkUntilDeadline = () => {
    setImmediate(performWorkUntilDeadline);
  };
} else {
  const channel = new MessageChannel();
  const port = channel.port2;
  channel.port1.onmessage = performWorkUntilDeadline;
  schedulePerformWorkUntilDeadline = () => {
    port.postMessage(null);
  };
}
  • 8.scheduledHostCallback 最终被赋值 callback,scheduler 在这里在这里到此结束

    接下来,我们看 reconciler,即上面图中的 workLoop

reconciler

  • 1.在上面 performSyncWorkOnRoot 的代码中,我们会看到
exitStatus = renderRootSync(root, lanes);

reconciler 也大致是从这里开始,接着看源码

// renderRootSync
do {
    try {
      workLoopSync();
      break;
    } catch (thrownValue) {
      handleError(root, thrownValue);
    }
  } while (true);

在 renderRootSync 中,我们可以看到,使用了 do…while 执行了 workLoopSync。

  • 2.workLoopSync 中循环执行了 performUnitOfWork,performUnitOfWork 中,开始执行 beginWork,其中,workInProgress 即是双缓存的对比树。
function workLoopSync() {
  // Already timed out, so perform work without checking if we need to yield.
  while (workInProgress !== null) {
    performUnitOfWork(workInProgress);
  }
}
function performUnitOfWork(unitOfWork: Fiber): void {
  // The current, flushed, state of this fiber is the alternate. Ideally
  // nothing should rely on this, but relying on it here means that we don't
  // need an additional field on the work in progress.
  const current = unitOfWork.alternate;
  setCurrentDebugFiberInDEV(unitOfWork);

  let next;
  if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
    startProfilerTimer(unitOfWork);
    next = beginWork(current, unitOfWork, subtreeRenderLanes);
    stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
  } else {
    next = beginWork(current, unitOfWork, subtreeRenderLanes);
  }

  resetCurrentDebugFiberInDEV();
  unitOfWork.memoizedProps = unitOfWork.pendingProps;
  if (next === null) {
    // If this doesn't spawn new work, complete the current work.
    completeUnitOfWork(unitOfWork);
  } else {
    workInProgress = next;
  }

  ReactCurrentOwner.current = null;
}
  • 3.beginWork 中,将 fiber 属性赋值,用于之后的双树对比,判断是否可以复用节点,具体逻辑即下面的 dom Diff
// 省略很多代码。。。。。。
case HostRoot:
      return updateHostRoot(current, workInProgress, renderLanes);
    case HostComponent:
      return updateHostComponent(current, workInProgress, renderLanes);
    case HostText:
      return updateHostText(current, workInProgress);
    case SuspenseComponent:
      return updateSuspenseComponent(current, workInProgress, renderLanes);
    case HostPortal:
      return updatePortalComponent(current, workInProgress, renderLanes);
  • 4.接着看上面的 completeUnitOfWork,completeUnitOfWork 遍历 fiber Tree,可以看到,优先深度遍历,一直遍历子节点,子节点不存在,开始遍历兄弟节点,即 sibling,sibling 没有,找 returnNode,即父节点
function completeUnitOfWork(unitOfWork: Fiber): void {
  // Attempt to complete the current unit of work, then move to the next
  // sibling. If there are no more siblings, return to the parent fiber.
  let completedWork = unitOfWork;
  do {
    // The current, flushed, state of this fiber is the alternate. Ideally
    // nothing should rely on this, but relying on it here means that we don't
    // need an additional field on the work in progress.
    const current = completedWork.alternate;
    const returnFiber = completedWork.return;

    // Check if the work completed or if something threw.
    if ((completedWork.flags & Incomplete) === NoFlags) {
      setCurrentDebugFiberInDEV(completedWork);
      let next;
      if (
        !enableProfilerTimer ||
        (completedWork.mode & ProfileMode) === NoMode
      ) {
        next = completeWork(current, completedWork, subtreeRenderLanes);
      } else {
        startProfilerTimer(completedWork);
        next = completeWork(current, completedWork, subtreeRenderLanes);
        // Update render duration assuming we didn't error.
        stopProfilerTimerIfRunningAndRecordDelta(completedWork, false);
      }
      resetCurrentDebugFiberInDEV();

      if (next !== null) {
        // Completing this fiber spawned new work. Work on that next.
        workInProgress = next;
        return;
      }
    } else {
      // This fiber did not complete because something threw. Pop values off
      // the stack without entering the complete phase. If this is a boundary,
      // capture values if possible.
      const next = unwindWork(completedWork, subtreeRenderLanes);

      // Because this fiber did not complete, don't reset its expiration time.

      if (next !== null) {
        // If completing this work spawned new work, do that next. We'll come
        // back here again.
        // Since we're restarting, remove anything that is not a host effect
        // from the effect tag.
        next.flags &= HostEffectMask;
        workInProgress = next;
        return;
      }

      if (
        enableProfilerTimer &&
        (completedWork.mode & ProfileMode) !== NoMode
      ) {
        // Record the render duration for the fiber that errored.
        stopProfilerTimerIfRunningAndRecordDelta(completedWork, false);

        // Include the time spent working on failed children before continuing.
        let actualDuration = completedWork.actualDuration;
        let child = completedWork.child;
        while (child !== null) {
          actualDuration += child.actualDuration;
          child = child.sibling;
        }
        completedWork.actualDuration = actualDuration;
      }

      if (returnFiber !== null) {
        // Mark the parent fiber as incomplete and clear its subtree flags.
        returnFiber.flags |= Incomplete;
        returnFiber.subtreeFlags = NoFlags;
        returnFiber.deletions = null;
      }
    }

    const siblingFiber = completedWork.sibling;
    if (siblingFiber !== null) {
      // If there is more work to do in this returnFiber, do that next.
      workInProgress = siblingFiber;
      return;
    }
    // Otherwise, return to the parent
    completedWork = returnFiber;
    // Update the next thing we're working on in case something throws.
    workInProgress = completedWork;
  } while (completedWork !== null);

  // We've reached the root.
  if (workInProgressRootExitStatus === RootIncomplete) {
    workInProgressRootExitStatus = RootCompleted;
  }
}
  • 5.接着 beginWork 中的 HostRoot 条件,执行 updateHostRoot
function updateHostRoot(current, workInProgress, renderLanes) {
    // .......
    reconcileChildren(current, workInProgress, nextChildren, renderLanes);
}
  • 6.reconcileChildren 中判断 current 是否存在,存在即更新,不存在即创建 fiber
export function reconcileChildren(
  current: Fiber | null,
  workInProgress: Fiber,
  nextChildren: any,
  renderLanes: Lanes,
) {
  if (current === null) {
    // If this is a fresh new component that hasn't been rendered yet, we
    // won't update its child set by applying minimal side-effects. Instead,
    // we will add them all to the child before it gets rendered. That means
    // we can optimize this reconciliation pass by not tracking side-effects.
    workInProgress.child = mountChildFibers(
      workInProgress,
      null,
      nextChildren,
      renderLanes,
    );
  } else {
    // If the current child is the same as the work in progress, it means that
    // we haven't yet started any work on these children. Therefore, we use
    // the clone algorithm to create a copy of all the current children.

    // If we had any progressed work already, that is invalid at this point so
    // let's throw it out.
    workInProgress.child = reconcileChildFibers(
      workInProgress,
      current.child,
      nextChildren,
      renderLanes,
    );
  }
}
    1. Dom Diff
    • 单节点
      • 1.判断 key 值是否相等,节点类型相同,可以复用
      • 2.key 或者节点类型不同,均标记删除
      • 3.不存在节点类型,即新增节点
      • 4.执行 renderWithHooks,这个在 useState 中看,这里会注入 hooks 上下文,执行函数体
        // 单节点
        function reconcileSingleElement(
        returnFiber: Fiber,
        currentFirstChild: Fiber | null,
        element: ReactElement,
        lanes: Lanes,
      ): Fiber {
        const key = element.key;
        let child = currentFirstChild;
        while (child !== null) {
          // TODO: If key === null and child.key === null, then this only applies to
          // the first item in the list.
          if (child.key === key) {
            const elementType = element.type;
            if (elementType === REACT_FRAGMENT_TYPE) {
              if (child.tag === Fragment) {
                deleteRemainingChildren(returnFiber, child.sibling);
                const existing = useFiber(child, element.props.children);
                existing.return = returnFiber;
                if (__DEV__) {
                  existing._debugSource = element._source;
                  existing._debugOwner = element._owner;
                }
                return existing;
              }
            } else {
              if (
                child.elementType === elementType ||
                // Keep this check inline so it only runs on the false path:
                (__DEV__
                  ? isCompatibleFamilyForHotReloading(child, element)
                  : false) ||
                // Lazy types should reconcile their resolved type.
                // We need to do this after the Hot Reloading check above,
                // because hot reloading has different semantics than prod because
                // it doesn't resuspend. So we can't let the call below suspend.
                (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;
                if (__DEV__) {
                  existing._debugSource = element._source;
                  existing._debugOwner = element._owner;
                }
                return existing;
              }
            }
            // Didn't match.
            deleteRemainingChildren(returnFiber, child);
            break;
          } else {
            deleteChild(returnFiber, child);
          }
          child = child.sibling;
        }
    
        if (element.type === REACT_FRAGMENT_TYPE) {
          const created = createFiberFromFragment(
            element.props.children,
            returnFiber.mode,
            lanes,
            element.key,
          );
          created.return = returnFiber;
          return created;
        } else {
          const created = createFiberFromElement(element, returnFiber.mode, lanes);
          created.ref = coerceRef(returnFiber, currentFirstChild, element);
          created.return = returnFiber;
          return created;
        }
    }
    
    • 多节点
      • 1.循环对比,判断旧节点的 index,如果旧节点的 index 大于新节点的 index,则需要等待新的 fiber
      • 2.对比新旧节点,index 与 key 如果相等,则返回节点,否则为 null
      • 3.判断节点是否存在移动,返回新的 index
      • 4.如果 oldFiber 是 null,则创建 fiber
      • 5.随后确认 ssp
        // 多节点
        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;
            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) {
                  // We matched the slot, but we didn't reuse the existing fiber, so we
                  // need to delete the existing child.
                  deleteChild(returnFiber, oldFiber);
                }
              }
              lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
              if (previousNewFiber === null) {
                // TODO: Move out of the loop. This only happens for the first run.
                resultingFirstChild = newFiber;
              } else {
                // TODO: Defer siblings if we're not at the right index for this slot.
                // I.e. if we had null values before, then we want to defer this
                // for each null value. However, we also don't want to call updateSlot
                // with the previous one.
                previousNewFiber.sibling = newFiber;
              }
              previousNewFiber = newFiber;
              oldFiber = nextOldFiber;
            }
            if (newIdx === newChildren.length) {
          // We've reached the end of the new children. We can delete the rest.
          deleteRemainingChildren(returnFiber, oldFiber);
          return resultingFirstChild;
        }
    
            if (oldFiber === null) {
              // If we don't have any more existing children we can choose a fast path
              // 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;
              }
              return resultingFirstChild;
            }
            // Add all children to a key map for quick lookups.
        const existingChildren = mapRemainingChildren(returnFiber, oldFiber);
    
        // Keep scanning and use the map to restore deleted items as moves.
        for (; newIdx < newChildren.length; newIdx++) {
          const newFiber = updateFromMap(
            existingChildren,
            returnFiber,
            newIdx,
            newChildren[newIdx],
            lanes,
          );
          if (newFiber !== null) {
            if (shouldTrackSideEffects) {
              if (newFiber.alternate !== null) {
                // The new fiber is a work in progress, but if there exists a
                // current, that means that we reused the fiber. We need to delete
                // it from the child list so that we don't add it to the deletion
                // list.
                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) {
              // Any existing children that weren't consumed above were deleted. We need
              // to add them to the deletion list.
              existingChildren.forEach(child => deleteChild(returnFiber, child));
            }
    
            return resultingFirstChild;
          }
    
  • 8.在上面的 beginWork 中,执行 completeWork,看下是如何创建 dom 节点的

function completeWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
): Fiber | null {
    // 省略其他component情况
    const instance = createInstance(
            type,
            newProps,
            rootContainerInstance,
            currentHostContext,
            workInProgress,
          );

          appendAllChildren(instance, workInProgress, false, false);

          workInProgress.stateNode = instance;

          // Certain renderers require commit-time effects for initial mount.
          // (eg DOM renderer supports auto-focus for certain elements).
          // Make sure such renderers get scheduled for later work.
          if (
            finalizeInitialChildren(
              instance,
              type,
              newProps,
              rootContainerInstance,
              currentHostContext,
            )
          ) {
            markUpdate(workInProgress);
          }
}
- 1.创建dom节点
- 2.将dom节点赋值给stateNode
- 3.将子节点插入instance中
- 4.标记更新

commit

负责将变化的组件渲染到页面上,分为三个阶段

  • 1.commitBeforeMutationEffects(Dom 操作前)

    处理 DOM 节点上的事件逻辑,dom 节点上的标记,递归调用自身,调用 getSnapshotBeforeUpdate 生命周期钩子

    - 进来就判断fiber.child,如果有就执行,所以子组件的getSnapshotBeforeUpdate会先执行
    
  • 2.commitMutationEffects(Dom 操作中) > 对于删除的组件,会执行 componentWillUnMount 钩子
  • 3.recursivelyCommitLayoutEffects(Dom 操作后)

    执行 useEffect,赋值 Ref,执行生命周期回调

    - 通过while判断child,递归执行useEffect,所以还是子组件会先执行
    
0