4

React 源码解析系列 - React 的 render 阶段(三):completeUnitOfWork

 2 years ago
source link: https://segmentfault.com/a/1190000040742732
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

系列文章目录(同步更新)

本系列文章均为讨论 React v17.0.0-alpha 的源码

performUnitOfWork

回忆《React 源码解析系列 - React 的 render 阶段(一):基本流程介绍》中介绍的 performUnitOfWork 方法:

function performUnitOfWork(unitOfWork: Fiber): void {
  const current = unitOfWork.alternate; // current树上对应的Fiber节点,有可能为null
  // ...省略

  let next; // 用来存放beginWork()返回的结果
  next = beginWork(current, unitOfWork, subtreeRenderLanes);

  // ...省略
  unitOfWork.memoizedProps = unitOfWork.pendingProps;
  if (next === null) { // beginWork返回null,表示无(或无需关注)当前节点的子Fiber节点
    completeUnitOfWork(unitOfWork);
  } else {
    workInProgress = next; // 下次的workLoopSync/workLoopConcurrent的while循环的循环主体为子Fiber节点
  }

  // ...省略
}

作为 render 的“归”阶段,需在 render 的“递”阶段结束后才会执行;换句话说,当 beginWork 返回 null 值,即当前节点无(或无需关注)当前节点的子Fiber节点时,才会进入到 render 的“归”阶段 —— completeUnitOfWork

completeUnitOfWork

下面来看本文的主角 —— completeUnitOfWork 方法:

function completeUnitOfWork(unitOfWork: Fiber): void {
  /*
    完成对当前Fiber节点的一些处理
    处理完成后,若当前节点尚有sibling节点,则结束当前方法,进入到下一次的performUnitOfWork的循环中
    若已没有sibling节点,则回退处理父节点(completedWork.return),
    直到父节点为null,表示整棵 workInProgress fiber 树已处理完毕。
   */
  let completedWork = unitOfWork;
  do {
    const current = completedWork.alternate;
    const returnFiber = completedWork.return;

    if ((completedWork.effectTag & Incomplete) === NoEffect) {
      let next;
      // ...省略
      next = completeWork(current, completedWork, subtreeRenderLanes);
      // ...省略
      
      /*
        假如completeWork返回不为空,则进入到下一次的performUnitOfWork循环中
        但这种情况太罕见,目前我只看到Suspense相关会有返回,因此此代码段姑且认为不会执行
       */
      if (next !== null) {
        workInProgress = next;
        return;
      }

      // ...省略

      if (
        returnFiber !== null &&
        (returnFiber.effectTag & Incomplete) === NoEffect
      ) {
        /* 收集所有带有EffectTag的子Fiber节点,以链表(EffectList)的形式挂载在当前节点上 */
        if (returnFiber.firstEffect === null) {
          returnFiber.firstEffect = completedWork.firstEffect;
        }
        if (completedWork.lastEffect !== null) {
          if (returnFiber.lastEffect !== null) {
            returnFiber.lastEffect.nextEffect = completedWork.firstEffect;
          }
          returnFiber.lastEffect = completedWork.lastEffect;
        }

        /* 如果当前Fiber节点(completedWork)也有EffectTag,那么将其放在(EffectList中)子Fiber节点后面 */
        const effectTag = completedWork.effectTag;
        /* 跳过NoWork/PerformedWork这两种EffectTag的节点,NoWork就不用解释了,PerformedWork是给DevTools用的 */
        if (effectTag > PerformedWork) {
          if (returnFiber.lastEffect !== null) {
            returnFiber.lastEffect.nextEffect = completedWork;
          } else {
            returnFiber.firstEffect = completedWork;
          }
          returnFiber.lastEffect = completedWork;
        }
      }
    } else {
      // 异常处理,省略...
    }

    // 取当前Fiber节点(completedWork)的兄弟(sibling)节点;
    // 如果有值,则结束completeUnitOfWork,并将该兄弟节点作为下次performUnitOfWork的主体(unitOfWork)
    const siblingFiber = completedWork.sibling;
    if (siblingFiber !== null) {
      workInProgress = siblingFiber;
      return;
    }
    // 若没有兄弟节点,则将在下次do...while循环中处理父节点(completedWork.return)
    completedWork = returnFiber;
    // 此处需要注意!
    // 虽然把workInProgress置为completedWork,但由于没有return,即没有结束completeUnitOfWork,因此没有意义
    // 直到completedWork(此时实际上是本循环中原completedWork.return)为null,结束do...while循环后
    // 此时completeUnitOfWork的运行结果(workInProgress)为null
    // 也意味着performSyncWorkOnRoot/performConcurrentWorkOnRoot中的while循环也达到了结束条件
    workInProgress = completedWork;
  } while (completedWork !== null);

  // 省略...
}

请看流程图:

react源码解析 - completeUnitOfWork流程图

由流程图可知, completeUnitOfWork 主要做了两件事:执行 completeWork收拢 EffectList ,下面详细介绍一下这两块内容。

completeWork

如果说“递”阶段的 beginWork 方法主要是创建子节点,那么“归”阶段的 completeWork 方法则主要是创建当前节点的 DOM 节点,并对子节点的 DOM 节点和 EffectList 进行收拢。
类似 beginWork , completeWork 也会根据当前节点不同的 tag 类型执行不同的逻辑:

function completeWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
): Fiber | null {
  const newProps = workInProgress.pendingProps;

  switch (workInProgress.tag) {
    case IndeterminateComponent:
    case LazyComponent:
    case SimpleMemoComponent:
    case FunctionComponent:
    case ForwardRef:
    case Fragment:
    case Mode:
    case Profiler:
    case ContextConsumer:
    case MemoComponent:
      return null;
    case ClassComponent: {
      // ...省略
      return null;
    }
    case HostRoot: {
      // ...省略
      return null;
    }
    case HostComponent: {
      // ...省略
      return null;
    }
  // ...省略
}

需要注意的是,很多类型的节点是没有 completeWork 这一块的逻辑的(即啥操作都没做就直接 return null),比如非常常见的 FragmentFunctionComponent 。我们重点关注页面渲染所必须的 HostComponent ,即由 HTML 标签(如 <div></div>)转换成的 Fiber 节点。

处理 HostComponent

function completeWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
): Fiber | null {
  const newProps = workInProgress.pendingProps;

  switch (workInProgress.tag) {
    // ...省略
    case HostComponent: {
      // ...省略
      const type = workInProgress.type;
      if (current !== null && workInProgress.stateNode != null) {
        updateHostComponent(
          current,
          workInProgress,
          type,
          newProps,
          rootContainerInstance,
        );

        // ...省略
      } else {
        // ...省略
        const instance = createInstance(
          type,
          newProps,
          rootContainerInstance,
          currentHostContext,
          workInProgress,
        );

        appendAllChildren(instance, workInProgress, false, false);
        workInProgress.stateNode = instance;

        if (
          finalizeInitialChildren(
            instance,
            type,
            newProps,
            rootContainerInstance,
            currentHostContext,
          )
        ) {
          markUpdate(workInProgress);
        }
      }
      return null;
    }
    // ...省略
  }
}

从上面这个代码段我们可以得知, completeWork 方法对 HostComponent 的处理主要有两个代码分支:

  • (current !== null && workInProgress.stateNode != null) === true 时,对当前节点做“更新”操作;
  • (current !== null && workInProgress.stateNode != null) === true 时,对当前节点做“新建”操作;

这里之所以没有用之前文章里常用的 mount(首屏渲染) 和 update 来表达,是因为存在一种情况,是 current !== nullworkInProgress.stateNode === null 的:在 update 时,假如当前的 Fiber 节点是个新的节点,已经在 beginWork 阶段被打上了 Placement effectTag ,那么就会存在 stateNode 为 null 的情况;而在这种情况下,同样需要做“新建”操作。

completeWork(处理 HostComponent 代码段)流程图

HostComponent 的“更新”操作

在此代码分支中,由于已经判断 workInProgress.stateNode !== null,即已存在对应的 DOM 节点,所以不需要再生成 DOM 节点。

我们可以看到这块主要是执行了一个 updateHostComponent 方法:

updateHostComponent = function(
  current: Fiber,
  workInProgress: Fiber,
  type: Type,
  newProps: Props,
  rootContainerInstance: Container,
) {
  /* 假如props没有变化(当前节点是通过bailoutOnAlreadyFinishedWork方法来复用的),可以跳过对当前节点的处理 */
  const oldProps = current.memoizedProps;
  if (oldProps === newProps) {
    return;
  }

  const instance: Instance = workInProgress.stateNode;
  // 省略...
  /* 计算需要变化的DOM节点属性,以数组方式存储(数组偶数索引的元素为属性名,数组基数索引的元素为属性值) */
  const updatePayload = prepareUpdate(
    instance,
    type,
    oldProps,
    newProps,
    rootContainerInstance,
    currentHostContext,
  );
  // 将计算出来的updatePayload挂载在workInProgress.updateQueue上,供后续commit阶段使用
  workInProgress.updateQueue = (updatePayload: any); 
  // 如果updatePayload不为空,则给当前节点打上Update的EffectTag
  if (updatePayload) {
    markUpdate(workInProgress);
  }
};

从上面的代码段可以看出 updateHostComponent 的主要作用就是计算出需要变化的 DOM 节点属性,并给当前节点打上Update的EffectTag。

prepareUpdate

接下来我们看 prepareUpdate 方法是如何计算出需要变化的 DOM 节点属性的:

export function prepareUpdate(
  domElement: Instance,
  type: string,
  oldProps: Props,
  newProps: Props,
  rootContainerInstance: Container,
  hostContext: HostContext,
): null | Array<mixed> {
  // 省略DEV代码...
  return diffProperties(
    domElement,
    type,
    oldProps,
    newProps,
    rootContainerInstance,
  );
}

可以看出 prepareUpdate 其实是直接调用了 diffProperties 方法。

diffProperties

diffProperties 方法的代码比较多,我这边就不放源码了,大概讲一下过程:

  1. 对特定 tag (由于本场景是处理 HostComponent ,因此 tag 即 html 标签名)的 lastProps & nextProps 做特殊处理,包括 input/select/textarea ,举例:input 的 value 值可能会是个 number ,而原生 input 的 value 只接受 string,因此这里需要转换数据类型。
  2. 遍历 lastProps:

    1. 如果该 prop 在 nextProps 中也存在,那么就跳过,相当于该 prop 没有变化,无需处理。
    2. 见到有 style 的 prop 就整理到 styleUpdates 变量(object)中,这部分 style 属性被置为空值
    3. 把除以上情况外的 propKey 推进一个数组(updatePayload)中,另外再推一个 null 值进数组中,表示把该 prop 清空掉。
  3. 遍历 nextProps:

    1. 如果该 nextProp 与 lastProp 一致,即更新前后没有发生变化,则跳过。
    2. 见到有 style 的 prop 就整理到 styleUpdates 变量中,注意这部分 style 属性是有值的
    3. 处理 DANGEROUSLY_SET_INNER_HTML
    4. 处理 children
    5. 除以上场景外,直接把 prop 的 key 和值都推进数组(updatePayload)中。
  4. 如果 styleUpdates 不为空,那么就把'style'和 styleUpdates 变量都推进数组(updatePayload)中。
  5. 返回 updatePayload。

updatePayload 是个数组,其中数组偶数索引的元素为 prop key ,数组基数索引的元素为 prop value

markUpdate

接着来看 markUpdate 方法,该方法其实很简单,就是在 workInProgress.effectTag 上打了个 Update EffectTag

function markUpdate(workInProgress: Fiber) {
  // Tag the fiber with an update effect. This turns a Placement into
  // a PlacementAndUpdate.
  workInProgress.effectTag |= Update;
}

HostComponent 的“新建”操作

“新建”操作的主要逻辑包括三个:

  • 为 Fiber 节点生成对应的 DOM 节点: createInstance 方法
  • 将子孙 DOM 节点插入刚生成的 DOM 节点中: appendAllChildren 方法
  • 初始化当前 DOM 节点的所有属性以及事件回调处理: finalizeInitialChildren 方法
createInstance

下面来看“为 Fiber 节点生成对应的 DOM 节点”的方法 —— createInstance

export function createInstance(
  type: string,
  props: Props,
  rootContainerInstance: Container,
  hostContext: HostContext,
  internalInstanceHandle: Object,
): Instance {
  let parentNamespace: string;
  // 省略DEV代码段...
  // 确定该DOM节点的命名空间(xmlns属性),一般是"http://www.w3.org/1999/xhtml"
  parentNamespace = ((hostContext: any): HostContextProd); 
  // 创建 DOM 元素
  const domElement: Instance = createElement(
    type,
    props,
    rootContainerInstance,
    parentNamespace,
  );
  // 在 DOM 对象上创建指向 fiber 节点对象的属性(指针),方便后续取用
  precacheFiberNode(internalInstanceHandle, domElement);
  // 在 DOM 对象上创建指向 props 的属性(指针),方便后续取用
  updateFiberProps(domElement, props);
  return domElement;
}

可以看出 createInstance 主要是调用了 createElement 方法来创建 DOM 元素;至于 createElement 本文不展开,有兴趣可以看看源码

appendAllChildren

下面来看“将子孙 DOM 节点插入刚生成的 DOM 节点中”的方法 —— appendAllChildren :

// completeWork是这样调用的:appendAllChildren(instance, workInProgress, false, false);

appendAllChildren = function(
  parent: Instance, // 相对于要append的子节点来说,completeWork当前处理的节点就是父节点
  workInProgress: Fiber,
  needsVisibilityToggle: boolean,
  isHidden: boolean,
) {
  let node = workInProgress.child; // 第一个子Fiber节点
  /* 这个while循环本质上是一个深度优先遍历 */
  while (node !== null) {
    if (node.tag === HostComponent || node.tag === HostText) {
      // 如果是html标签或纯文本对应的子节点,则将当前子节点的DOM添加到父节点的DOM子节点列表末尾
      appendInitialChild(parent, node.stateNode);
    } else if (enableFundamentalAPI && node.tag === FundamentalComponent) { // 先忽略
      appendInitialChild(parent, node.stateNode.instance);
    } else if (node.tag === HostPortal) {
      // ...无操作
    } else if (node.child !== null) {
      // 针对一些特殊类型的子节点,如<Fragment />,尝试从子节点的子节点获取DOM
      node.child.return = node; // 设置好return指针,方便后续辨别是否达到循环结束条件
      node = node.child; // 循环主体由子节点变为子节点的子节点
      continue; // 立即开展新一轮的循环
    }
    if (node === workInProgress) {
      return; // 遍历“回归时”发现已经达到遍历的结束条件,结束遍历
    }
    // 若当前循环主体node已无兄弟节点(sibling),则进行“回归”;且如果“回归”一次后发现还是没有sibling,将继续“回归”
    while (node.sibling === null) {
      if (node.return === null || node.return === workInProgress) {
        return; // “回归”过程中达到遍历的结束条件,结束遍历
      }
      node = node.return; // “回归”的结果:将node.return作为下次循环的主体
    }
    // 走到这里就表明当前循环主体有sibling
    node.sibling.return = node.return; // 设置好return指针,方便后续辨别是否达到循环结束条件
    node = node.sibling; // 将node.sibling作为下次循环的主体
  }
};

// appendInitialChild本质上就是执行了appendChild这个原生的DOM节点方法
// https://developer.mozilla.org/zh-CN/docs/Web/API/Node/appendChild
export function appendInitialChild(parentInstance: Instance, child: Instance | TextInstance): void {
  parentInstance.appendChild(child);
}

appendAllChildren 本质上是一个有条件限制(限制递进层次)的深度优先遍历:

  1. 取出当前节点(parent)的第一个子节点作为循环主体(node)。
  2. 如果该循环主体是 html 标签或纯文本对应的 Fiber 节点,则将其 DOM appendChildparent
  3. 如果当前循环主体(node)有兄弟节点(node.sibling),则将该兄弟节点设为下次循环的主体。

光看上面这个流程,这不是一个典型的广度优先遍历吗?别急,因为还有一种比较特殊的情况:当当前循环主体不是 html 标签或纯文本对应的 Fiber 节点,且当前循环主体有子节点(node.child)时,将当前循环主体的子节点作为下次循环的主体,并立即开始下次循环(continue)。

以下面这个组件作为例子:

function App() {
    return (
        <div>
            <b>1</b>
            <Fragment>
                <span>2</span>
                <p>3</p>
            </Fragment>
        </div>
    )
}

根据《React 源码解析系列 - React 的 render 阶段(一):基本流程介绍》里对 beginWork 和 completeWork 的执行顺序可以得出:

1. rootFiber beginWork 
2. App Fiber beginWork 
3. div Fiber beginWork 
4. b Fiber beginWork 
5. b Fiber completeWork // 当前节点 —— <b />, appendChild 文本节点
6. Fragment Fiber beginWork
7. span Fiber beginWork
8. span Fiber completeWork // 当前节点 —— <span />, appendChild 文本节点
9. p Fiber beginWork
10. p Fiber completeWork  // 当前节点 —— <p />, appendChild 文本节点
11. Fragment Fiber completeWork // 跳过
12. div Fiber completeWork // 下面我们来重点介绍这一块
13. App Fiber completeWork
14. rootFiber completeWork

我们来重点介绍 div 节点中的 appendAllChildren

  1. while 循环执行前初始化:取出 div 节点的第一个子节点 —— b 节点,作为第一次 while 循环的主体。
  2. 第一次 while 循环(循环主体为 b 节点):

    1. b 节点是一个 HostComponent ,直接 appendChild 。
    2. b 节点有一个兄弟节点,即 Fragment 节点,将其设置为下一次 while 循环的主体(node)。
  3. 第二次 while 循环(循环主体为 Fragment 节点):

    1. 由于 Fragment 节点既不是 HostComponent 也不是 HostText ,因此将取 Fragment 节点的第一个子节点 —— span 节点作为下次 while 循环的主体(node)。
    2. 立即进入(continue)下一次 while 循环。
  4. 第三次 while 循环(循环主体为 span 节点):

    1. span 节点是一个 HostComponent ,直接 appendChild 。
    2. span 节点有一个兄弟节点,即 p 节点,将其设置为下一次 while 循环的主体(node)。
  5. 第四次 while 循环(循环主体为 p 节点):

    1. p 节点是一个 HostComponent ,直接 appendChild 。
    2. p 节点没有兄弟节点,进行回归(node = node.return),此时在该“回归”代码段 —— 一个小 while 循环中,循环主体变为 p 节点的父节点,即 Fragment 节点。
    3. 继续下一次小 while 循环:由于 Fragment 也没有兄弟节点,不满足小 while 循环的结束条件,因此继续进行“回归”,此时循环主体(node)为 div 节点。
    4. 继续下一次小 while 循环:由于 div 节点满足node.return === workInProgress,因此直接结束整个遍历过程 —— appendAllChildren。
finalizeInitialChildren

下面来看“初始化当前 DOM 节点的所有属性以及事件回调处理” —— finalizeInitialChildren

export function finalizeInitialChildren(
  domElement: Instance,
  type: string,
  props: Props,
  rootContainerInstance: Container,
  hostContext: HostContext,
): boolean {
  setInitialProperties(domElement, type, props, rootContainerInstance);
  return shouldAutoFocusHostComponent(type, props);
}

从上面的代码段,我们可以很清晰地看到 finalizeInitialChildren 主要分为两个步骤:

  1. 执行 setInitialProperties 方法;注意,该方法与 prepareUpdate 不一样,该方法是会真正将 DOM 属性挂载到 DOM 节点上的,也会真正地调用 addEventListener 把事件处理回调绑定在当前 DOM 节点上的。
  2. 执行 shouldAutoFocusHostComponent 方法:返回 props.autoFocus 的值(仅 button / input / select / textarea 支持)。

收拢 EffectList

作为 DOM 操作的依据,commit 阶段需要找到所有带有 effectTag 的 Fiber 节点并依次执行effectTag 对应操作,难道还需要在 commit 阶段再遍历一次 Fiber 树吗?这显然是很低效的。

为了解决这个问题,在 completeUnitOfWork 中,每个执行完 completeWork 且存在 effectTag 的 Fiber 节点会被保存在一条被称为 effectList 的单向链表中; effectList 中第一个 Fiber 节点保存在 fiber.firstEffect ,最后一个元素保存在 fiber.lastEffect 。

类似 appendAllChildren ,在“归”阶段,所有有 effectTag 的 Fiber 节点都会被追加在父节点的 effectList 中,最终形成一条以 rootFiber.firstEffect 为起点的单向链表。

如果当前 Fiber 节点(completedWork)也有 EffectTag ,那么将其放在( EffectList 中)子 Fiber 节点的后面。

/* 如果父节点的effectList头指针为空,那么就直接把本节点的effectList头指针赋给父节点的头指针,相当于把本节点的整个effectList直接挂在父节点中 */
if (returnFiber.firstEffect === null) {
    returnFiber.firstEffect = completedWork.firstEffect;
}
/* 如果父节点的effectList不为空,那么就把本节点的effectList挂载在父节点effectList的后面 */
if (completedWork.lastEffect !== null) {
    if (returnFiber.lastEffect !== null) {
    returnFiber.lastEffect.nextEffect = completedWork.firstEffect;
    }
    returnFiber.lastEffect = completedWork.lastEffect;
}

/* 如果当前Fiber节点(completedWork)也有EffectTag,那么将其放在(EffectList中)子Fiber节点后面 */
const effectTag = completedWork.effectTag;
/* 跳过NoWork/PerformedWork这两种EffectTag的节点,NoWork就不用解释了,PerformedWork是给DevTools用的 */
if (effectTag > PerformedWork) {
  if (returnFiber.lastEffect !== null) {
     returnFiber.lastEffect.nextEffect = completedWork;
  } else {
     returnFiber.firstEffect = completedWork;
  }
     returnFiber.lastEffect = completedWork;
  }
}

completeUnitOfWork 结束

completeUnitOfWork 有两种结束的场景:

  • 当前节点(completed)有兄弟节点(completed.sibling),此时会将 workInProgress(即 performUnitOfWork 的循环主体)设为该兄弟节点,然后结束掉 completeUnitOfWork 方法,此后将进行下一次 performUnitOfWork ,换句话说:执行该“兄弟节点”的“递”阶段 —— beginWork 。
  • 在 completeUnitOfWork “回归”的过程中, completed 的值为 null ,即当前已完成整棵 Fiber 树的回归;此时, workInProgress 的值为 null ,这意味着 workLoopSync / workLoopConcurrent 方法中的 while 循环也到达了结束条件;至此, React 的 render 阶段结束。

当 render 阶段结束时,在 performSyncWorkOnRoot 方法中,会调用 commitRoot(root) 来开启 React commit 阶段的工作。


Recommend

  • 37

    这是我的剖析 React 源码的第二篇文章,如果你没有阅读过之前的文章,请务必先阅读一下 第一篇文章 中提到的一些注意事项,能帮助你更好地阅读源码。 现在请大家打开 我的代码 并定位到 react-dom 文件夹下的 src 中的 ReactDOM.js 文件

  • 38

    这是我的剖析 React 源码的第三篇文章,如果你没有阅读过之前的文章,请务必先阅读一下 第一篇文章 中提到的一些注意事项,能帮助你更好地阅读源码。 文章相关资料 React 16.8.6 源码中文注释,这个链接是文章的核心,文中的具体代码及代码行数都是依托

  • 4

    该系列文章是本人在学习 Spring 的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring 源码分析 GitHub 地址 进行阅读 Sprin...

  • 4

    https://github.com/go-chi/chigo-chi介绍chi是用于构建Go HTTP服务的轻巧,惯用且可组合的路由器。它特别擅长帮助您编写大型REST API服务,这些服务可随着项目的增长和更改而保持可维护性。chi它...

  • 6

    React 的工作流程React 的工作流程主要分为 render 和 commit 两个阶段:render 阶段根据 JSX 转化成的 ReactElement (更准确地来说,jsx 经 babel 转化后是 React.createElement() 的代码段,在 render 阶段该代码段被执行后便生成了对...

  • 6

    系列文章目录(同步更新)本系列文章均为讨论 React v17.0.0-alpha 的源码下面来介绍 React Render 的“递”阶段 —— beginWork ,在《React 源码解析系列 - Re...

  • 3
    • segmentfault.com 2 years ago
    • Cache

    React commit阶段解析-前置学习

    在renderRootSync执行完render相关阶段后,就会进入commit阶段。performSyncWorkOnRoot函数执行的末尾调用commitRoot(root);commit阶段工作在 rootFiber.firstEffect 上保存了一条需要执行副作用的 Fiber 节点的单向链表effectList...

  • 6
    • segmentfault.com 2 years ago
    • Cache

    react源码解析8.render阶段

    react源码解析8.render阶段视频讲解(高效学习):

  • 2

    系列文章目录(同步更新)本系列文章均为讨论 React v17.0.0-alpha 的源码错误边界(Error Boundaries)在解释 React 内部实现前,我想先从一个 React API —— 错误边界(Error Boundaries)...

  • 5
    • www.xiabingbao.com 1 year ago
    • Cache

    React18 源码解析之 render()入口方法

    整个react源码结构太过庞大,就像一个毛线团,我们总得先找到一个头,才能抽丝剥茧地梳理。 我们解析的源码是 React18.1.0 版本,请注意版本号。React 源码学习的 GitHub 仓库地址:

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK