您现在的位置是:首页 >技术杂谈 >react从render开始与内部执行与实现(一)网站首页技术杂谈

react从render开始与内部执行与实现(一)

清风笑~ 2023-05-16 12:00:02
简介react从render开始与内部执行与实现(一)
学习笔记react17中render方法内部执行与实现以root节点为例

react-dom中render方法

React.render(<App/>, document.getElementById('root'));

在react-dom模块中index.js文件里找到render方法进入ReactDOMLegacy.js模块

export {
  createPortal,
  unstable_batchedUpdates,
  flushSync,
  __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
  version,
  findDOMNode,
  hydrate,
  render,
  unmountComponentAtNode,
  createRoot,
  createRoot as unstable_createRoot,
  createBlockingRoot,
  createBlockingRoot as unstable_createBlockingRoot,
  unstable_flushControlled,
  unstable_scheduleHydration,
  unstable_runWithPriority,
  unstable_renderSubtreeIntoContainer,
  unstable_createPortal,
  unstable_createEventHandle,
  unstable_isNewReconciler,
} from './src/client/ReactDOM';

ReactDOMLegacy模块render方法

/**
 * 接受react组件把它转换成真真实的dom元素
 * @param {*} element 虚拟dom
 * @param {*} container dom元素/容器元素
 * @param {*} callback 
 * @returns 
 */
export function render(
  element: React$Element<any>,
  container: Container,
  callback: ?Function,
) {
  invariant(
    isValidContainer(container),
    'Target container is not a DOM element.',
  );
  // 将虚拟dom渲染成真实的dom挂载到容器上
  return legacyRenderSubtreeIntoContainer(
    null,
    element,
    container,
    false,
    callback,
  );
}

legacyRenderSubtreeIntoContainer方法,判断是否有root节点是否存在,没有就创建,同时都会调用updateContainer方法来更新DOM元素,最后通过getPublicRootInstance根节点的实例

/**
 * 接受react组件把它转换成真真实的dom元素
 * @param {*} parentComponent 渲染的 React 元素所属的父组件
 * @param {*} children 虚拟dom
 * @param {*} container 容器元素  document.getElementById('root')
 * @param {*} forceHydrate 是否为不同的根元素(不同的根元素需要卸载原来的DOM再挂载新的DOM)
 * @param {*} callback 回调函数
 * @returns 
 */
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) {
  	// 没有root节点时创建root节点
  	// 获取容器元素上_reactRootContainer属性  forceHydrate是否在服务端渲染
    root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
      container,
      forceHydrate,
    );
    fiberRoot = root._internalRoot;
    if (typeof callback === 'function') {
      const originalCallback = callback;
      callback = function() {
        const instance = getPublicRootInstance(fiberRoot);
        originalCallback.call(instance);
      };
    }
    // 初始化时不能批量运行
    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);
      };
    }
    // 新的虚拟DOM转换成真实的DOM元素并且更新到页面上
    updateContainer(children, fiberRoot, parentComponent, callback);
  }
  // 返回根节点的实例
  return getPublicRootInstance(fiberRoot);
}

legacyCreateRootFromDOMContainer最终调用createFiberRoot方法返回FiberRoot并初始化更新队列initializeUpdateQueue此处tagexport const LegacyRoot = 0;

export function createFiberRoot(
  containerInfo: any,
  tag: RootTag,
  hydrate: boolean,
  hydrationCallbacks: null | SuspenseHydrationCallbacks,
): FiberRoot {
  const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);
  if (enableSuspenseCallback) {
    root.hydrationCallbacks = hydrationCallbacks;
  }

  // 创建一个未初始化的 HostRootFiber 对象,并将其赋值给 FiberRoot 的 current 属性。
  // 同时,将 FiberRoot 对象赋值给 HostRootFiber 的 stateNode 属性,以便它们可以互相引用
  const uninitializedFiber = createHostRootFiber(tag);
  root.current = uninitializedFiber;
  uninitializedFiber.stateNode = root;

  initializeUpdateQueue(uninitializedFiber);

  return root;
}

updateContainer更新DOM元素

updateContainer 方法用于将一个 React 元素更新到指定的 DOM 容器中,并返回更新任务的优先级即一个Lane 对象。Lane 是 React 中用于调度更新任务的概念,不同的任务会被分配到不同的 Lane 中,以保证任务的优先级和执行顺序。

/**
 * 接受react组件把它转换成真真实的dom元素
 * @param {*} children 虚拟dom
 * @param {*} container 容器元素  document.getElementById('root')  legacyCreateRootFromDOMContainer返回实例
 * @param {*} 渲染的 React 元素所属的父组件
 * @param {*} callback 回调函数
 * @returns 
 */
export function updateContainer(
  element: ReactNodeList,
  container: OpaqueRoot,
  parentComponent: ?React$Component<any, any>,
  callback: ?Function,
): Lane {
  // 获取容器的 Fiber 对象
  // 即方法创建的 const uninitializedFiber = createHostRootFiber(tag); root.current = uninitializedFiber;
  const current = container.current;
  // 获取当前时间
  const eventTime = requestEventTime();
  // 根据优先级获取更新任务对应的 Lane
  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);
  // Caution: React DevTools currently depends on this property 将要更新的 React 元素设置到更新对象中
  // being called "element".
  update.payload = {element};
  // 将更新对象加入到任务队列中,等待执行。在任务执行时,会遍历 Fiber 树,比较新旧两棵树的差异,然后根据差异进行相应的更新操作
  enqueueUpdate(current, update);
  // 调度更新任务 current有stateNode字段,stateNode值为root 即FiberRootNode(containerInfo)创建
  scheduleUpdateOnFiber(current, lane, eventTime);

  return lane;
}

scheduleRootUpdate触发Fiber树的更新

export function scheduleUpdateOnFiber(
  fiber: Fiber,
  lane: Lane,
  eventTime: number,
) {
 // 用于检查是否有嵌套的更新
  checkForNestedUpdates();
  // 将更新标记从Fiber节点向上移动到其根节点。这是为了确保React可以找到要更新的根节点,并对其进行更新。如果没有找到根节点,则函数返n
  const root = markUpdateLaneFromFiberToRoot(fiber, lane);
  if (root === null) {
    return null;
  }
  // 用于标记根节点已更新,并将更新的优先级和事件时间记录在根节点上
  markRootUpdated(root, lane, eventTime);
  // 判断要更新的根节点是当前正在进行的工作的根节点
  if (root === workInProgressRoot) {
    if (
      deferRenderPhaseUpdateToNextBatch ||
      (executionContext & RenderContext) === NoContext
    ) {
    // deferRenderPhaseUpdateToNextBatch为真,说明当前的更新任务需要推迟到下一个batch中执行
    // mergeLanes函数合并更新的优先级,可以保证更新被正确地排序和处理
      workInProgressRootUpdatedLanes = mergeLanes(
        workInProgressRootUpdatedLanes,
        lane,
      );
    }
    // workInProgressRootExitStatus为RootSuspendedWithDelay时,将root标记为暂停状态
    // 具体来说,当渲染一个fiber树时,如果该fiber树被挂起(如等待异步数据)则会将
    // workInProgressRootExitStatus标记为RootSuspendedWithDelay。
    if (workInProgressRootExitStatus === RootSuspendedWithDelay) {
      // 用于将根节点标记为已经挂起,不会尝试渲染该根节点的任何更新,直到挂起的数据可用
      // markRootSuspended函数有两个参数:root(FiberNode对象)和suspended lanes(表示当前已挂起的lane的位掩码)。它将root的
      // pendingLanes设置为suspended lanes,并将其到期时间设置为Sync,这表示应尽快处理更新。
      markRootSuspended(root, workInProgressRootRenderLanes);
    }
  }
  const priorityLevel = getCurrentPriorityLevel();
  if (lane === SyncLane) {
 
    if ((executionContext & LegacyUnbatchedContext) !== NoContext &&
      (executionContext & (RenderContext | CommitContext)) === NoContext
    ) {
      // 当前执行上下文为 LegacyUnbatchedContext,且不在渲染或提交上下文中,则直接执行同步任务
      schedulePendingInteractions(root, lane);
      performSyncWorkOnRoot(root);
    } else {
      
      ensureRootIsScheduled(root, eventTime);
      schedulePendingInteractions(root, lane);
      if (executionContext === NoContext) {
        resetRenderTimer();
        flushSyncCallbackQueue();
      }
    }
  } else {
   // 如果是异步更新模式,则提交异步更新任务
    if (
      (executionContext & DiscreteEventContext) !== NoContext &&
      (priorityLevel === UserBlockingSchedulerPriority ||
        priorityLevel === ImmediateSchedulerPriority)
    ) {
      if (rootsWithPendingDiscreteUpdates === null) {
        rootsWithPendingDiscreteUpdates = new Set([root]);
      } else {
        rootsWithPendingDiscreteUpdates.add(root);
      }
    }
    // 上面updateContainer方法中 container.current
    // 根据优先级根节点更新
    ensureRootIsScheduled(root, eventTime);
    schedulePendingInteractions(root, lane);
  }
  mostRecentlyUpdatedRoot = root;
}

ensureRootIsScheduled根节点跟新任务

该方法检查现有任务的优先级是否与根节点下一级工作的优先级相同,这个函数在更新时或任务结束之前被调用

function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
  const existingCallbackNode = root.callbackNode;

  // 检测lanes是否被占用,被占用标记过期
  markStarvedLanesAsExpired(root, currentTime);

  // 获取任务优先级
  const nextLanes = getNextLanes(
    root,
    root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
  );
  
  const newCallbackPriority = returnNextLanesPriority();
  // 没任务进行释放
  if (nextLanes === NoLanes) {
    if (existingCallbackNode !== null) {
      cancelCallback(existingCallbackNode);
      root.callbackNode = null;
      root.callbackPriority = NoLanePriority;
    }
    return;
  }

  // Check if there's an existing task. We may be able to reuse it.
  if (existingCallbackNode !== null) {
    const existingCallbackPriority = root.callbackPriority;
    if (existingCallbackPriority === newCallbackPriority) {
      // The priority hasn't changed. We can reuse the existing task. Exit.
      return;
    }
    // 任务优先级改变了 开启新任务
    cancelCallback(existingCallbackNode);
  }

  // 新任务
  let newCallbackNode;
  if (newCallbackPriority === SyncLanePriority) {
    //  指定任务类型 export const SyncLanePriority: LanePriority = 15;
    newCallbackNode = scheduleSyncCallback(
      performSyncWorkOnRoot.bind(null, root),
    );
  } else if (newCallbackPriority === SyncBatchedLanePriority) {
   // 任务类型  export const SyncBatchedLanePriority: LanePriority = 14;
   // ImmediateSchedulerPriority 最高优先级
    newCallbackNode = scheduleCallback(
      ImmediateSchedulerPriority,
      performSyncWorkOnRoot.bind(null, root),
    );
  } else {
   // 获取对应的优先级
    const schedulerPriorityLevel = lanePriorityToSchedulerPriority(
      newCallbackPriority,
    );
    newCallbackNode = scheduleCallback(
      schedulerPriorityLevel,
      performConcurrentWorkOnRoot.bind(null, root),
    );
  }

  root.callbackPriority = newCallbackPriority;
  root.callbackNode = newCallbackNode;
}

performConcurrentWorkOnRoot为并发入口,通过它调度程序(Scheduler)执行任务,每一个更新和渲染任务都是一个并发任务,并由Scheduler管理

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。