React 源码解析 - 更新流程 renderRoot 渲染阶

2019-06-03  本文已影响0人  coolheadedY

往期回顾

React 源码解析 React 的更新

React 源码解析 - React 创建更新回顾和 React 的批量更新

React 源码解析 - 调度模块原理 - 实现 requestIdleCallback �

React 源码解析 - reactScheduler 异步任务调度

renderRoot 入口

function renderRoot(root: FiberRoot, isYieldy: boolean, isExpired: boolean) {
  // ...
  if ( // 将要执行的任务 root 和 expirationTime 和 nextRenderExpirationTime、nextRoot 预期的不一样, 应该是之前任务被高优先级的任务打断了。
    expirationTime !== nextRenderExpirationTime ||
    root !== nextRoot ||
    nextUnitOfWork === null // 更新结束 fiber 的 child,下一个节点, 首次 = null
  ) {
  // 初始化的内容
    resetStack(); // 重置
    nextRoot = root;
    nextRenderExpirationTime = expirationTime; // root.nextExpirationTimeToWorkOn;
    nextUnitOfWork = createWorkInProgress( // 拷贝了一份 fiber 对象操作
      nextRoot.current,
      null,
      nextRenderExpirationTime,
    );
    root.pendingCommitExpirationTime = NoWork; // 设置成 NoWork
  // ...
  }
// 开始进入 workLoop 
  do {
    try {
      workLoop(isYieldy); // 进行每个节点的更新
    } catch (thrownValue) {
      // ...
      break; // 遇到了某种错误跳出
    }   while(true)

}

workLoop 中所有发生的错误都会被 render 阶段 catch,render 阶段会根据捕获的错误具体内容进行相应的操作

function workLoop(isYieldy) {
  if (!isYieldy) { // 不可中断 Sync 和 超时任务不可中断
    // Flush work without yielding
    // nextUnitOfWork 是 fiber 对象,为 null 已经是 root 节点 fiber return 的 null 了
    // 用于记录render阶段Fiber树遍历过程中下一个需要执行的节点。在resetStack中分别被重置,他只会指向workInProgress
    while (nextUnitOfWork !== null) { // 不停的更新,不为 null 就不停执行 next 的 child 的更新
      nextUnitOfWork = performUnitOfWork(nextUnitOfWork); // 进行更新
    }
  } else {
    // Flush asynchronous work until the deadline runs out of time.
    while (nextUnitOfWork !== null && !shouldYield()) { // 判断 shouldYield = false 当前时间片是否有时间更新
      nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    }
  }
}

workLoop 只是根据时间片是否有任务调用 performUnitOfWork 进行更新, 只有当 nextUnitOfWork === null 时才代表任务已经更新完了



performUnitOfWork 在 beginWork 中对当前 fiber 进行更新,更新到此 fiber 的最后时会去找兄弟节点,最后返回给 workLoop 中的 while(nextUnitOfWork) 中继续执行

beginWork

核心功能

第一步验证当前 fiber 树是否需要更新

根据 fiber 的 tag 类型进行更新

进行更新先把当前 fiber 的 expirationTime 设置为 NoWork,根据 tag 进行不同组件的更新


workInProgress 更新所用到的 fiber 对象属性

更新函数会用到的参数

FunctionComponent 更新

function updateFunctionComponent(
  current,
  workInProgress,
  Component,
  nextProps: any,
  renderExpirationTime,
) {
  // ... context 相关
  let nextChildren;
  // Component 组件方法,这里就是我们声明组件的方式 function(props, context) {}
  nextChildren = Component(nextProps, context); 
  // 把 nextChildren 这些 ReactElement 变成 Fiber 对象, 在 workInProgress.child 挂载 fiber  
  reconcileChildren( 
    current,
    workInProgress,
    nextChildren,
    renderExpirationTime,
  );
  return workInProgress.child;
}

reconcileChildren

核心点

根据首次渲染或更新渲染进行操作

export const reconcileChildFibers = ChildReconciler(true);
export const mountChildFibers = ChildReconciler(false);

reconcileChildFibers

placeSingleChild


更新渲染时 placeSingleChild 会把新创建的 fiber 节点标记为 Placement, 待到提交阶段处理



其中 ReactElement, Portal, TextNode 三种类型的节点需要进行处理x

reconcileSingleElement 更新 ReactElement

调和单个子节点

第一段逻辑,从原 fiber 节点的兄弟节点遍历,比较 fiber 节点和 nextChilren key 值

第二段逻辑,没有可以复用的节点,根据 elment nextChildren 的类型创建 Fragment 或者 Element 类型的 fiber。

reconcileSingleTextNode 更新 textNode

useFiber 创建复用的节点


reconcileChildrenArray 调和数组 children

第一次遍历(优化加速)

updateSlot

textNode 文本节点

ReactElement 节点和 isArray 数组节点

break 或者遍历完毕后的情况

情况一

情况二

核心通用操作,构建 map 复用

更新 classComponent 组件

首次渲染 class 组件

constructClassInstance 创建实例

function constructClassInstance(
  workInProgress: Fiber,
  ctor: any,
  props: any,
  renderExpirationTime: ExpirationTime,
): any {
  // ...
  // 从这里开始,ctor 就是 element.type 的 Compnent,这里生成 class 组件实例
  const instance = new ctor(props, context);
  const state = (workInProgress.memoizedState = // memoizedState 为实例的 state, 没有就为 null
    instance.state !== null && instance.state !== undefined
      ? instance.state
      : null);
  adoptClassInstance(workInProgress, instance);
  // ...
  return instance;
}
// 为实例确定 updater 对象
function adoptClassInstance(workInProgress: Fiber, instance: any): void {
  instance.updater = classComponentUpdater; // 给 class 组件实例的 updater 设置
  workInProgress.stateNode = instance; // instance 赋值给当前 workInProgress.stateNode
  ReactInstanceMap.set(instance, workInProgress); // 给 instance._reactInternalFiber 赋值当前的 fiber 对象
}

mountClassInstance 首次挂载实例

processUpdateQueue 计算更新 state

resumeMountClassInstance 复用实例

updateClassInstance 更新实例

finishClassComponent 完成 class 组件更新

没错误捕获又不需要更新

捕获错误的操作

最后执行的 reconcileChildren

IndeterminateComponent 更新

更新 HostComponent 原生 dom 标签

updateHostComponent

shouldSetTextContent

HostText 文本节点

updateHostText

文本内容不需要构建 fiber 结构,直接在提交阶段更新就行了

function updateHostText(current, workInProgress) {
  if (current === null) {
    tryToClaimNextHydratableInstance(workInProgress);
  }
  // Nothing to do here. This is terminal. We'll do the completion step
  // immediately after.
  return null; // 文本没有子节点不需要调和, 到 提交阶段直接就更新了
}

ForwardRef 更新

Mode 组件

Memo 组件

初次渲染

export function isSimpleFunctionComponent(type: any) {
  return (
    typeof type === 'function' &&
    !shouldConstruct(type) &&
    type.defaultProps === undefined
  );
}

更新渲染

没有调和 reconcileChildren


reconcileChildren 也是把 nextChildren 结果的 ReactElement 生成 fiber 后赋值给 workInprogress.child 上不过多了很多 类型的判断, memo 组件有必要更新是直接创建后 赋值在 workInprogress.child 上了,memo 组件编写只会返回常规的 ReactElement 组件内容

completeUnitOfWork

完成节点更新,重置 childExpirationTime

构建 effect 链,供 commitRoot 提交阶段使用

单侧节点查找向上寻找节点

performUnitOfWork 遍历 fiber 树的顺序

总结

参考文章

实现 fiber 架构

上一篇 下一篇

猜你喜欢

热点阅读