React 源码之状态更新(二)

·  阅读 498
React 源码之状态更新(二)

渲染及更新

经过前面这么多章的铺垫,今天我们终于可以看看React的起点ReactDOM.render,在React18之前这是唯一入口函数,在React18中新增了createRoot().render()入口。

ReactDOM.render函数创建的程序都是legacy模式,而有createRoot().render()创建的程序为concurrent模式。

那么我们下面来看看两种模式入口的异同

Legacy模块

Legacy模块是React18之前一直采取的模式,主要是同步更新。其模式的入口为ReactDOM.render

export function render(
  element: React$Element<any>,
  container: Container,
  callback: ?Function,
) {
	// ...忽略部分代码
  // 创建fiberRoot和rootFiber
  return legacyRenderSubtreeIntoContainer(
    null,
    element,
    container,
    false,
    callback,
  );
}
复制代码

可以看出render函数极其简单,只是调用Legacy模式的入口函数legacyRenderSubtreeIntoContainer

render函数共有三个参数:

  • element:是我们创建的App应用
  • container:DOM 容器
  • callback:首次渲染完成后,会调用callback

入口

作为legacy模式入口函数legacyRenderSubtreeIntoContainer做什么了呢?legacyRenderSubtreeIntoContainer函数主要做一下几件事:

  • 调用legacyCreateRootFromDOMContainer创建FiberRoot和rootFiber,FiberRoot全局唯一,而rootFiber不一定
  • 判断是否为mount阶段,如果为mount阶段,创建FiberRoot,并将FiberRoot挂载到container上
  • 如果有callback函数,处理callback函数的this问题
  • 调用updateContainer,启动后续流程
function legacyRenderSubtreeIntoContainer(
  parentComponent: ?React$Component<any, any>,
  children: ReactNodeList,
  container: Container,
  forceHydrate: boolean,
  callback: ?Function,
) {
  if (__DEV__) {
    topLevelUpdateWarnings(container);
    warnOnInvalidCallback(callback === undefined ? null : callback, 'render');
  }

  // TODO: Without `any` type, Flow says "Property cannot be accessed on any
  // member of intersection type." Whyyyyyy.
  let root: RootType = (container._reactRootContainer: any);
  let fiberRoot;
  // 根据root是否存在,判断是否更新或mount
  if (!root) {
    // Initial mount
    // container默认会挂载FiberRoot
    root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
      container,
      forceHydrate,
    );
    // fiberRoot
    fiberRoot = root._internalRoot;
    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
    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);
}
复制代码

FiberRoot和RootFiber创建

legacyCreateRootFromDOMContainerfunction legacyCreateRootFromDOMContainer(
  container: Container,
  forceHydrate: boolean,
): RootType {
  // 判断当前模式是否为ssr
  const shouldHydrate =
    forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
  // First clear any existing content.
  if (!shouldHydrate) {
    let warned = false;
    let rootSibling;
    while ((rootSibling = container.lastChild)) {
      // ...省略dev代码
      // 删除container中的children
      container.removeChild(rootSibling);
    }
  }
  // ...省略dev代码
	// 创建fiberRoot
  return createLegacyRoot(
    container,
    shouldHydrate
      ? {
          hydrate: true,
        }
      : undefined,
  );
}

export function createLegacyRoot(
  container: Container,
  options?: RootOptions,
): RootType {
  // 创建RootFiber,并且模式为LegacyRoot
  return new ReactDOMBlockingRoot(container, LegacyRoot, options);
}
复制代码

FiberRoot和RootFiber是相互引用的,FiberRoot.current -> RootFiber,而RootFiber.stateNode -> FiberRoot

ReactDOMBlockingRoot函数中调用链如下:

create-fiber.png

在new ReactDOMBlockingRoot时,传入的模式为LegacyRoot

createFiberRoot函数中会调用new FiberRootNode创建FiberRoot,调用createHostRootFiber创建rootFiber,同时调用initializeUpdateQueue来初始化RootFiber的UpdateQueue类型。

至此我们初始化工作完成,下面我们将进行update的创建,触发一次新的更新

update

update的入口函数为updateContainer,代码如下:

unbatchedUpdates(() => {
  // 调用updateContainer,创建update,触发一次更新
  updateContainer(children, fiberRoot, parentComponent, callback);
});


export function updateContainer(
  element: ReactNodeList,
  container: OpaqueRoot,
  parentComponent: ?React$Component<any, any>,
  callback: ?Function,
): Lane {
  // ...省略dev
  // root Fiber
  const current = container.current;
  const eventTime = requestEventTime();
  // ...省略dev
  // 获取更新优先级
  const lane = requestUpdateLane(current);

  if (enableSchedulingProfiler) {
    markRenderScheduled(lane);
  }
	// 处理context
  const context = getContextForSubtree(parentComponent);
  if (container.context === null) {
    container.context = context;
  } else {
    container.pendingContext = context;
  }

  // ...省略dev

  // 创建update
  const update = createUpdate(eventTime, lane);
  // update payload 为app
  update.payload = {element};
	// 处理callback
  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;
  }
	// 将update加入到updateQueue
  enqueueUpdate(current, update);
  // 调度更新
  scheduleUpdateOnFiber(current, lane, eventTime);

  return lane;
}
复制代码

updateContainer主要做一下几件事:

  • 获取本次更新的lane优先级
  • 处理context
  • 调用createUpdate创建update
  • 调用enqueueUpdate将update加入到updateQueue中
  • 调度更新(scheduleUpdateOnFiber

调度更新

在react中所有的更新都要走调度系统,而调度系统入口就是scheduleUpdateOnFiber。此函数比较复杂,我会对代码进行注释:

export function scheduleUpdateOnFiber(
  fiber: Fiber,
  lane: Lane,
  eventTime: number,
) {
  // 检测代码,可以忽略
  checkForNestedUpdates();
  warnAboutRenderPhaseUpdatesInDEV(fiber);

  // 获取到fiberroot,react中,所有的更新都是从root开始的
  const root = markUpdateLaneFromFiberToRoot(fiber, lane);
  // 如果fiberroot为null,直接跳出当前更新
  if (root === null) {
    warnAboutUpdateOnUnmountedFiberInDEV(fiber);
    return null;
  }

  // Mark that the root has a pending update.
  // 将root设置为待更新的状态
  markRootUpdated(root, lane, eventTime);
  // 这个if条件是为了处理渲染中断的情况,后期讲concurrent的时候,会详细讲这块
  if (root === workInProgressRoot) {
    // Received an update to a tree that's in the middle of rendering. Mark
    // that there was an interleaved update work on this root. Unless the
    // `deferRenderPhaseUpdateToNextBatch` flag is off and this is a render
    // phase update. In that case, we don't treat render phase updates as if
    // they were interleaved, for backwards compat reasons.
    if (
      deferRenderPhaseUpdateToNextBatch ||
      (executionContext & RenderContext) === NoContext
    ) {
      workInProgressRootUpdatedLanes = mergeLanes(
        workInProgressRootUpdatedLanes,
        lane,
      );
    }
    if (workInProgressRootExitStatus === RootSuspendedWithDelay) {
      // The root already suspended with a delay, which means this render
      // definitely won't finish. Since we have a new update, let's mark it as
      // suspended now, right before marking the incoming update. This has the
      // effect of interrupting the current render and switching to the update.
      // TODO: Make sure this doesn't override pings that happen while we've
      // already started rendering.
      markRootSuspended(root, workInProgressRootRenderLanes);
    }
  }

  // 获取当前的优先级,react有五个优先级,后期在前concurrent模式的时候会讲
  // 没有优先级
	// export const NoPriority = 0;
	// 立即执行的优先级,同步执行的优先级
	// export const ImmediatePriority = 1;
	// 用户优先级,用户触发的优先级
	// export const UserBlockingPriority = 2;
	// 正常优先级,比如:请求数据
	// export const NormalPriority = 3;
 	// 低优先级
	// export const LowPriority = 4;
	// 空闲优先级
	// export const IdlePriority = 5;
  const priorityLevel = getCurrentPriorityLevel();
	// 同步任务
  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);

      // This is a legacy edge case. The initial mount of a ReactDOM.render-ed
      // root inside of batchedUpdates should be synchronous, but layout updates
      // should be deferred until the end of the batch.
      // 同步更新
      // root -> fiberRoot.current
      performSyncWorkOnRoot(root);
    } else {
      ensureRootIsScheduled(root, eventTime);
      schedulePendingInteractions(root, lane);
      if (executionContext === NoContext) {
        // Flush the synchronous work now, unless we're already working or inside
        // a batch. This is intentionally inside scheduleUpdateOnFiber instead of
        // scheduleCallbackForFiber to preserve the ability to schedule a callback
        // without immediately flushing it. We only do this for user-initiated
        // updates, to preserve historical behavior of legacy mode.
        resetRenderTimer();
        flushSyncCallbackQueue();
      }
    }
  } else { // 异步任务
    // Schedule a discrete update but only if it's not Sync.
    if (
      (executionContext & DiscreteEventContext) !== NoContext &&
      // Only updates at user-blocking priority or greater are considered
      // discrete, even inside a discrete event.
      (priorityLevel === UserBlockingSchedulerPriority ||
        priorityLevel === ImmediateSchedulerPriority)
    ) {
      // This is the result of a discrete event. Track the lowest priority
      // discrete update per root so we can flush them early, if needed.
      if (rootsWithPendingDiscreteUpdates === null) {
        rootsWithPendingDiscreteUpdates = new Set([root]);
      } else {
        rootsWithPendingDiscreteUpdates.add(root);
      }
    }
    // 异步更新任务的入口,此函数中会调用performConcurrentWorkOnRoot
    ensureRootIsScheduled(root, eventTime);
    schedulePendingInteractions(root, lane);
  }

  // We use this when assigning a lane for a transition inside
  // `requestUpdateLane`. We assume it's the same as the root being updated,
  // since in the common case of a single root app it probably is. If it's not
  // the same root, then it's not a huge deal, we just might batch more stuff
  // together more than necessary.
  mostRecentlyUpdatedRoot = root;
}
复制代码

至此为止,我们的流程可以全部串起来了,整个流程如下:

flow.png

其他模式

React18以后React将有三种模式:

  • legacy: ReactDOM.render入口创建的模式
  • blocking:开启部分concurrent模式特性的中间模式。目前正在实验中。作为迁移到concurrent模式的第一个步骤。
  • concurrent:由createRoot().render()创建的模式。面向未来的开发模式。我们之前讲的任务中断/任务优先级都是针对concurrent模式。

以上三种模式的支持:

legacyblockingconcurrent
String Refs
Legacy Context
findDOMNode
Suspense
SuspenseList
Suspense SSR + Hydration
Progressive Hydration
Selective Hydration
Cooperative Multitasking
Automatic batching of multiple setStates
Priority-based Rendering
Interruptible Prerendering
useTransition
useDeferredValue
Suspense Reveal "Train"

this.setState

有了前面的支持,我们理解setState就很容易了。

Component.prototype.setState = function(partialState, callback) {
  invariant(
    typeof partialState === 'object' ||
      typeof partialState === 'function' ||
      partialState == null,
    'setState(...): takes an object of state variables to update or a ' +
      'function which returns an object of state variables.',
  );
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
复制代码

有上面的代码,可以看出,setState最终会调用this.updater.enqueueSetState,下面我们看this.updater.enqueueSetState函数。

this.updater.enqueueSetState函数在ReactFiberClassComponent.*.js中,代码如下:

enqueueSetState(inst, payload, callback) {
    const fiber = getInstance(inst);
    const eventTime = requestEventTime();
    const lane = requestUpdateLane(fiber);

    const update = createUpdate(eventTime, lane);
    update.payload = payload;
    if (callback !== undefined && callback !== null) {
      if (__DEV__) {
        warnOnInvalidCallback(callback, 'setState');
      }
      update.callback = callback;
    }

    enqueueUpdate(fiber, update);
    scheduleUpdateOnFiber(fiber, lane, eventTime);

    if (__DEV__) {
      if (enableDebugTracing) {
        if (fiber.mode & DebugTracingMode) {
          const name = getComponentName(fiber.type) || 'Unknown';
          logStateUpdateScheduled(name, lane, payload);
        }
      }
    }

    if (enableSchedulingProfiler) {
      markStateUpdateScheduled(fiber, lane);
    }
  },
复制代码

由代码可以看出,在enqueueSetState只做了两件事:

  • 创建update
  • 调用scheduleUpdateOnFiber

至此,我们更新流程全部完成,下一章我们就进入Hooks

分类:
前端
标签: