React Fiber框架中的Render渲染阶段——workLoop(performUnitOfWork【beginWork与completeWork】)

触发渲染过程------renderRoot

renderRoot 是一个函数,用于触发渲染工作。它通常会调用并递归地执行一系列的渲染任务,直到完成整个更新过程。这个过程包括执行 Fiber 树中的 beginWork 和 completeWork,以及渲染新状态或 DOM。

bash 复制代码
function renderRoot(root: FiberRootNode) {
	//双缓存机制,将current复制一层给workInProgress
	//React 使用两棵 Fiber 树(current 和 workInProgress)来实现双缓存
  const { current } = root; // 获取当前的 Fiber 树的根节点
  let workInProgress = current;

  // 启动渲染任务(并发渲染模式下会启动任务)
  workInProgress = performUnitOfWork(workInProgress);

  // 继续调度工作单元
  while (workInProgress !== null) {
    workInProgress = performUnitOfWork(workInProgress);
  }
}

react源码解析8.render阶段

renderRoot

React 的渲染过程可以分为多个阶段,包括:更新(Reconciliation)、渲染(Rendering)、提交(Commit)等。

bash 复制代码
//更细节的
function renderRoot(root: FiberRootNode) {
  try {
    // 初始化
    prepareFreshStack(root);
 
    // 开始工作循环
    do{
	   try{
	      workLoop();
          break;
  } catch (e) {
    console.warn('workLoop发生错误', e);
    workInProgress = null;
  }while (true); 
 
  // 获取完成的工作单元
  const finishedWork = root.current.alternate;
  root.finishedWork = finishedWork; 
 
  // 提交根节点的wip fiberNode树,并处理flags
  commitRoot(root);

prepareFreshStatck()

prepareFreshStatck()中的createWorkInProgress简单理解就是上面将current复制一层给workInProgress, const { current } = root; // 获取当前的 Fiber 树的根节点 let workInProgress = current;,两棵 Fiber 树来实现双缓存

复制过后的双缓存 数据结构

workLoop()

bash 复制代码
function workLoop(){
	while(workInProgress !== null)
	performUnitOfWork(workInProgress);
}

performUnitOfWork(workInProgress) 【递归】

负责递归遍历 Fiber 树,并根据不同的 Fiber 类型执行相应的更新或渲染逻辑

bash 复制代码
function performUnitOfWork(fiber: FiberNode) {
  // 开始对当前Fiber节点进行工作
  // 这里的"递"可能指的是递归处理子节点
  const next = beginWork(fiber); // 执行beginWork函数,返回下一个需要处理的Fiber节点
 
  // 更新当前Fiber节点的memoizedProps为pendingProps
  // 这通常意味着将传入的props"确认"为当前节点的属性
  fiber.memoizedProps = fiber.pendingProps;
 
  // 判断下一个需要处理的节点是否为null
  // 如果为null,可能表示当前节点没有子节点或子节点已经处理完毕
  if (next === null) {
    // 这里的"归"可能指的是回溯到父节点,完成当前节点的工作
    completeUnitOfWork(fiber); // 执行completeUnitOfWork函数,完成当前Fiber节点的工作
  } else {
    // 如果还有下一个节点需要处理,则更新workInProgress指针
    // workInProgress通常指向当前正在处理的Fiber节点
    workInProgress = next; // 更新workInProgress为下一个需要处理的Fiber节点
    // 注意:这里通常会有一个递归调用performUnitOfWork(next),但在您提供的代码片段中省略了
  }
}

递归结束状态模型

beginWork流程(递)
  1. 建立节点的父子以及兄弟节点关联关系
    child return sibling属性
  2. 给fiber节点打上flag标记(当前节点的flag)

beginWork 主要发生在协调阶段,它被调用来处理和更新 React 虚拟 DOM(Fiber 树)。根据节点类型(HostRoot、FunctionComponent等)调用对应更新函数

1.初始化 Fiber 节点的工作:当 React 开始处理某个 Fiber 节点时,beginWork 会被调用。它会检查该节点的当前状态,并决定是否需要进行更新。

2.调度子节点的更新:如果当前节点有子节点,beginWork 会为这些子节点安排进一步的更新工作。

在执行过程中,beginWork 会遍历 Fiber 树,判断是否需要更新(例如,检查 props 或 state 是否发生了变化),然后决定是否继续向下渲染(即继续对子节点调用 beginWork)

bash 复制代码
export const beginWork = (wip: FiberNode): FiberNode | null => {
  // 根据Fiber节点的类型,执行相应的更新逻辑,并返回下一个需要处理的Fiber节点
  switch (wip.tag) {
    case 'HostRoot':
      return updateHostRoot(wip); // 处理根节点
    case 'HostComponent':
      return updateHostComponent(wip); // 处理宿主组件(如DOM元素)
    case 'HostText':
      return null; // 文本节点不需要进一步处理,直接返回null
    case 'FunctionComponent':
      return updateFunctionComponent(wip); // 处理函数组件
    default:
      console.warn('beginWork未实现的类型'); // 输出警告信息
      break; // 注意:这里的break是多余的,因为return已经会退出函数
      // 但为了保持格式一致性和清晰性,我们暂时保留它
      return null; // 对于未实现的类型,返回null
  }

beginWork初次执行完, 此时内存状态,wip和h1对应的fiber对象建立联系,并且给h1 fiber打上flags标记

updateHostRoot

更新队列(processUpdateQueue【memorizedState=element元素】)、协调子元素(reconcileChildren)

bash 复制代码
function updateHostRoot(wip: FiberNode): FiberNode | null {
  // 获取当前工作单元(Fiber)的基态
  const baseState = wip.memoizedState as Element; // 假设memoizedState是Element类型
 
  // 获取更新队列,并断言其类型为UpdateQueue<Element>
  const updateQueue = wip.updateQueue as UpdateQueue<Element>;
 
  // 从共享对象中取出待处理的更新,并清空待处理队列
  const pending = updateQueue.shared.pending;
  updateQueue.shared.pending = null;
 
  // 使用processUpdateQueue函数处理待处理的更新,并获取更新后的状态
  //执行函数获取element对象
  const { memoizedState } = processUpdateQueue(baseState, pending) as { memoizedState: Element };
 
  // 更新当前工作单元的基态为最新状态
  wip.memoizedState = memoizedState;
 
  // 获取更新后的子节点,这里假设memoizedState直接代表了子节点
  // 注意:这里的逻辑可能需要根据实际情况调整,因为memoizedState可能并不直接等于子节点
  const nextChildren = wip.memoizedState as Element[]; // 假设这里是Element数组,但需要根据实际情况确定
 
  // 调用reconcileChildren函数来协调(渲染)子节点
  // 注意:函数名可能是reconcileChildren的一个拼写错误,通常应该是reconcileChildren或者类似的名称,但这里按照您提供的名称使用
  reconcileChildren(wip, nextChildren);
 
  // 返回当前工作单元的第一个子节点,以便后续的工作单元可以继续处理
  // 注意:如果wip.child是null,则表示没有子节点需要处理
  return wip.child;
}
 
processUpdateQueue
bash 复制代码
export const processUpdateQueue = <state>(
  baseState: state,
  pendingUpdate: Update<state> | null
): { memoizedState: state } => {
  // 初始化结果对象,其memoizedState属性设置为baseState
  const result: { memoizedState: state } = {
    memoizedState: baseState
  };
 
  // 检查是否有待处理的更新
  if (pendingUpdate !== null) {
    const action = pendingUpdate.action;
 
    // 如果action是一个函数,则执行它并更新memoizedState
    if (typeof action === 'function') {
      result.memoizedState = action(baseState);
    } else {
      // 如果action不是函数,则直接将其值赋给memoizedState
      // 注意:这里假设action的类型与state兼容
      result.memoizedState = action as state; // 需要类型断言来确保TypeScript不会报错
    }
  }
 
  // 返回结果对象
  return result;
};


** 注:Fiber对象数据结构 **

reconcileChildren(★★★)

reconcileChildren 主要处理组件的子树,对于每一个子节点(即子 Fiber 节点)会执行以下操作:

  1. 子节点的类型判断(会首先判断每个子节点的类型【比如是 DOM 元素、函数组件还是类组件等】,然后根据不同的类型来决定如何处理)
  2. 节点的比较相同类型的节点/不同类型的节点/key 和索引
  3. 生成新的 Fiber 节点(为需要更新或新创建的子组件生成新的 Fiber 节点)
  4. 处理子树的递归(beginWork中递归调用)(会递归地调用自己来处理子组件。如果某个子组件有子节点,React 会继续对子节点进行协调,直到所有节点都被处理完)

    reconcileChildFibers|mountChildFibers
    创建子fiber的过程会进入reconcileChildren,该函数的作用是为workInProgress fiber节点生成它的child fiber即 workInProgress.child。然后继续深度优先遍历它的子节点执行相同的操作。mountChildFibers,reconcileChildFibers和mountChildFibers最终其实就是ChildReconciler传递不同的参数返回的函数,这个参数用来表示是否追踪副作用.
bash 复制代码
function ChildReconciler(shouldTrackSideEffects) {
	function placeChild(newFiber, lastPlacedIndex, newIndex) {
    newFiber.index = newIndex;

    if (!shouldTrackSideEffects) {//是否追踪副作用
      // Noop.
      return lastPlacedIndex;
    }

    var current = newFiber.alternate;

    if (current !== null) {
      var oldIndex = current.index;

      if (oldIndex < lastPlacedIndex) {
        // This is a move.
        newFiber.flags = Placement;
        return lastPlacedIndex;
      } else {
        // This item can stay in place.
        return oldIndex;
      }
    } else {
      // This is an insertion.
      newFiber.flags = Placement;
      return lastPlacedIndex;
    }
  }
}
bash 复制代码
const App:any=function (){
	return(
	<h1>
		<h2>
			<h3>3333</h3>
		</h2>
	</h1>
	)
}

初次被调用执行, 此时内存状态,wip和h1对应的fiber对象建立联系,并且给h1 fiber打上flags标记

递归,直至next指向为null

completeWork流程(归)

主要执行任务:

1.创建真实dom节点,但是仍在内存中,未渲染到页面

2.处理flag与subtreeFlags标记子树标识,用"|"运算处理)

3.建立真实DOM关系,将子元素插入父元素中

bash 复制代码
function completeWork(current, workInProgress) {
  switch (workInProgress.tag) {
    case 'HostComponent': {
      // 如果是普通的 DOM 节点
      if (!workInProgress.stateNode) {
        // 如果没有对应的 DOM 实例,创建一个新的
        const domElement = document.createElement(workInProgress.type);

        // 为 DOM 元素添加属性
        const props = workInProgress.pendingProps;
        for (const key in props) {
          if (key === 'children') {
            // 如果是文本内容,直接设置
            if (typeof props[key] === 'string' || typeof props[key] === 'number') {
              domElement.textContent = props[key];
            }
          } else if (key.startsWith('on')) {
            // 添加事件监听器(如 onClick)
            const eventType = key.toLowerCase().substring(2);
            domElement.addEventListener(eventType, props[key]);
          } else {
            // 设置其他属性
            domElement.setAttribute(key, props[key]);
          }
        }

        // 将 DOM 实例存储在 stateNode 中
        workInProgress.stateNode = domElement;
      }
      return null;
    }

    case 'FunctionComponent':
    case 'ClassComponent': {
      // 函数组件和类组件在 completeWork 中通常不需要特殊处理
      return null;
    }

    default:
      return null;
  }
}
相关推荐
佩淇呢22 分钟前
CSS响应式
前端·css
林涧泣36 分钟前
【Uniapp-Vue3】onUnload页面卸载和onPageScroll页面监听滚动
前端·javascript·uni-app
Xudde.1 小时前
HTML中meta的用法
java·前端·javascript·笔记·html
傻小胖2 小时前
react的statehook useState Hook详细使用
前端·javascript·react.js
).(2 小时前
el-table横向滚动条,滚动后消失
前端·css·css3
zhuangvi2 小时前
Element Plus 之 el-table相同行合并(通用函数),相同列合并(自行判断需合并的字段以及相应的列下标)
elementui·前端框架
微臣酒驾来迟2 小时前
el-descriptions-item使用span占行不生效
前端·javascript·vue.js
WebInfra2 小时前
Build System 视角:重新认识前端打包工具的设计哲学
前端·设计模式·webpack
明月看潮生2 小时前
青少年编程与数学 02-006 前端开发框架VUE 22课题、状态管理
前端·javascript·vue.js·青少年编程·编程与数学
禾小毅2 小时前
vue 实现打包并同时上传至服务器端
前端·vue.js