欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Fiber 树的构建

程序员文章站 2022-03-06 10:36:09
我们先来看一个简单的 demo: import * as React from 'react'; import * as ReactDOM from 'react-dom'; class App extends React.Component { render() { return (

我们先来看一个简单的 demo:

import * as react from 'react';
import * as reactdom from 'react-dom';
class app extends react.component {
    render() {
        return (
            <div classname="container">
                <div classname="section">
                    <h1>this is the title.</h1>
                    <p>this is the first paragraph.</p>
                    <p>this is the second paragraph.</p>
                </div>
            </div>
        );
    }
}
reactdom.render(<app />, document.getelementbyid('root'));

首次渲染的调用栈如下图

Fiber 树的构建

以 performsyncworkonroot 和 commitroot 两个方法为界限,可以把 reactdom.render 分为三个阶段:

  1. init
  2. render
  3. commit

init phase

render

很简单,直接调用 legacyrendersubtreeintocontainer。

export function render(
  element: react$element<any>,
  container: container,
  callback: ?function,
) {
  // 省略对 container 的校验逻辑
  return legacyrendersubtreeintocontainer(
    null,
    element,
    container,
    false,
    callback,
  );
}

这里需要注意一点,此时的 element 已经不是 render 中传入的 了,而是经过 react.createelement 转换后的一个 reactelement 对象。

legacyrendersubtreeintocontainer

在这里我们可以看到方法取名的重要性,一个好的方法名可以让你一眼就看出这个方法的作用。legacyrendersubtreeintocontainer,顾名思义,这是一个遗留的方法,作用是渲染子树并将其挂载到 container 上。再来看一下入参,children 和 container 分别是之前传入 render 方法的 app 元素和 id 为 root 的 dom 元素,所以可以看出这个方法会根据 app 元素生成对应的 dom 树,并将其挂在到 root 元素上。

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 = container._reactrootcontainer = legacycreaterootfromdomcontainer(
      container,
      forcehydrate,
    );
    fiberroot = root._internalroot;
	// 省略对 callback 的处理逻辑
    unbatchedupdates(() => {
      updatecontainer(children, fiberroot, parentcomponent, callback);
    });
  } else {
    // 省略 else 逻辑
  }
  return getpublicrootinstance(fiberroot);
}

下面来细看一下这个方法:

  1. 首次挂载时,会通过 legacycreaterootfromdomcontainer 方法创建 container.reactrootcontainer 对象并赋值给 root。 container 对象现在长这样:

Fiber 树的构建

  1. 初始化 fiberroot 为 root.internalroot,类型为 fiberrootnode。fiberroot 有一个极其重要的 current 属性,类型为 fibernode,而 fibernode 为 fiber 节点的对应的类型。所以说 current 对象是一个 fiber 节点,不仅如此,它还是我们要构造的 fiber 树的头节点,我们称它为 rootfiber。到目前为止,我们可以得到下图的指向关系:

Fiber 树的构建

  1. 将 fiberroot 以及其它参数传入 updatecontainer 形成回调函数,将回调函数传入 unbatchedupdates 并调用。

unbatchedupdates

主要逻辑就是调用回调函数 fn,也就是之前传入的 updatecontainer。

export function unbatchedupdates<a, r>(fn: (a: a) => r, a: a): r {
  const prevexecutioncontext = executioncontext;
  executioncontext &= ~batchedcontext;
  executioncontext |= legacyunbatchedcontext;
  try {
	// fn 为之前传入的 updatecontainer
    return fn(a);
  } finally {
    executioncontext = prevexecutioncontext;
    if (executioncontext === nocontext) {
      resetrendertimer();
      flushsynccallbackqueue();
    }
  }
}

updatecontainer

updatecontainer 方法做的还是一些杂活,我们简单总结一下:

  1. 计算当前 fiber 节点的 lane(优先级)。
  2. 根据 lane(优先级),创建当前 fiber 节点的 update 对象,并将其入队。
  3. 调度当前 fiber 节点(rootfiber)。
export function updatecontainer(
  element: reactnodelist,
  container: opaqueroot,
  parentcomponent: ?react$component<any, any>,
  callback: ?function,
): lane {
  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;
  }
  // 根据 lane(优先级)计算当前节点的 update 对象
  const update = createupdate(eventtime, lane);
  update.payload = {element};
  callback = callback === undefined ? null : callback;
  if (callback !== null) {
    update.callback = callback;
  }
  // 将 update 对象入队
  enqueueupdate(current, update);
  // 调度当前 fiber节点(rootfiber)
  scheduleupdateonfiber(current, lane, eventtime);
  return lane;
}

scheduleupdateonfiber

接着会进入 scheduleupdateonfiber 方法,根据 lane(优先级)等于 synclane,代码最终会执行 performsyncworkonroot 方法。performsyncworkonroot 翻译过来,就是指执行根节点(rootfiber)的同步任务,所以 reactdom.render 的首次渲染其实是一个同步的过程。

Fiber 树的构建

到这里大家可能会有个疑问,为什么 reactdom.render 触发的首次渲染是一个同步的过程呢?不是说在新的 fiber 架构下,render 阶段是一个可打断的异步过程。
我们先来看看 lane 是怎么计算得到的,相关逻辑在 updatecontainer 中的 requestupdatelane 方法里:

export function requestupdatelane(fiber: fiber): lane {
  const mode = fiber.mode;
  if ((mode & blockingmode) === nomode) {
    return (synclane: lane);
  } else if ((mode & concurrentmode) === nomode) {
    return getcurrentprioritylevel() === immediateschedulerpriority
      ? (synclane: lane)
      : (syncbatchedlane: lane);
  } else if (
    !deferrenderphaseupdatetonextbatch &&
    (executioncontext & rendercontext) !== nocontext &&
    workinprogressrootrenderlanes !== nolanes
  ) {
   return pickarbitrarylane(workinprogressrootrenderlanes);
  }
  // 省略非核心代码
}

可以看出 lane 的计算是由当前 fiber 节点(rootfiber)的 mode 属性决定的,这里的 mode 属性其实指的就是当前 fiber 节点的渲染模式,而 rootfiber 的 mode 属性其实最终是由 react 的启动方式决定的。
react 其实有三种启动模式:

  • legacy mode: reactdom.render(<app />, rootnode)。这是目前 react app 使用的方式,当前没有删除这个模式的计划,但是这个模式不支持一些新的功能。
  • blocking mode:reactdom.createblockingroot(rootnode).render(<app />)。目前正在实验中,作为迁移到 concurrent 模式的第一个步骤。
  • concurrent mode: reactdom.createroot(rootnode).render(<app />)。目前正在实验中,在未来稳定之后,将作为 react 的默认启动方式。此模式启用所有新功能。

因此不同的渲染模式在挂载阶段的差异,本质上来说并不是工作流的差异(其工作流涉及 初始化 → render → commit 这 3 个步骤),而是 mode 属性的差异。mode 属性决定着这个工作流是一气呵成(同步)的,还是分片执行(异步)的。

render phase

performsyncworkonroot

核心是调用 renderrootsync 方法

renderrootsync

有两个核心方法 preparefreshstack 和 workloopsync,下面来逐个分析。

preparefreshstack

首先调用 preparefreshstack 方法,preparefreshstack 中有一个重要的方法 createworkinprogress。

export function createworkinprogress(current: fiber, pendingprops: any): fiber {
  let workinprogress = current.alternate;
  if (workinprogress === null) {
	// 通过 current 创建 workinprogress
    workinprogress = createfiber(
      current.tag,
      pendingprops,
      current.key,
      current.mode,
    );
    workinprogress.elementtype = current.elementtype;
    workinprogress.type = current.type;
    workinprogress.statenode = current.statenode;
	// 使 workinprogress 与 current 通过 alternate 相互指向
    workinprogress.alternate = current;
    current.alternate = workinprogress;
  } else {
	// 省略 else 逻辑
  }
  // 省略对 workinprogress 属性的处理逻辑
  return workinprogress;
}

下面我们来看一下 workinprogress 究竟是什么?workinprogress 是 createfiber 的返回值,接着来看一下 createfiber。

const createfiber = function(
  tag: worktag,
  pendingprops: mixed,
  key: null | string,
  mode: typeofmode,
): fiber {
  return new fibernode(tag, pendingprops, key, mode);
};

可以看出 createfiber 其实就是在创建一个 fiber 节点。所以说 workinprogress 其实就是一个 fiber 节点。
从 createworkinprogress 中,我们还可以看出:

  1. workinprogress 节点是 current 节点(rootfiber)的一个副本。
  2. workinprogress 节点与 current 节点(rootfiber)通过 alternate 属性相互指向。

所以到现在为止,我们的 fiber 树如下:

Fiber 树的构建

workloopsync

接下来调用 workloopsync 方法,代码很简单,若 workinprogress 不为空,调用 performunitofwork 处理 workinprogress 节点。

function workloopsync() {
  while (workinprogress !== null) {
    performunitofwork(workinprogress);
  }
}

performunitofwork

performunitofwork 有两个重要的方法 beginwork 和 completeunitofwork,在 fiber 的构建过程中,我们只需重点关注 beginwork 这个方法。

function performunitofwork(unitofwork: fiber): void {
  const current = unitofwork.alternate;
  setcurrentdebugfiberindev(unitofwork);
  let next;
  if (enableprofilertimer && (unitofwork.mode & profilemode) !== nomode) {
    startprofilertimer(unitofwork);
    next = beginwork(current, unitofwork, subtreerenderlanes);
    stopprofilertimerifrunningandrecorddelta(unitofwork, true);
  } else {
    next = beginwork(current, unitofwork, subtreerenderlanes);
  }
  resetcurrentdebugfiberindev();
  unitofwork.memoizedprops = unitofwork.pendingprops;
  if (next === null) {
    completeunitofwork(unitofwork);
  } else {
    workinprogress = next;
  }
  reactcurrentowner.current = null;
}

目前我们只能看出,它会对当前的 workinprogress 节点进行处理,至于怎么处理的,当我们解析完 beginwork 方法再来总结 performunitofwork 的作用。

beginwork

根据 workinprogress 节点的 tag 进行逻辑分发。tag 属性代表的是当前 fiber 节点的类型,常见的有下面几种:

  • functioncomponent:函数组件(包括 hooks)
  • classcomponent:类组件
  • hostroot:fiber 树根节点
  • hostcomponent:dom 元素
  • hosttext:文本节点
function beginwork(
  current: fiber | null,
  workinprogress: fiber,
  renderlanes: lanes,
): fiber | null {
  // 省略非核心(针对树构建)逻辑
  switch (workinprogress.tag) {
	// 省略部分 case 逻辑
	// 函数组件(包括 hooks)
    case functioncomponent: {
      const component = workinprogress.type;
      const unresolvedprops = workinprogress.pendingprops;
      const resolvedprops =
        workinprogress.elementtype === component
          ? unresolvedprops
          : resolvedefaultprops(component, unresolvedprops);
      return updatefunctioncomponent(
        current,
        workinprogress,
        component,
        resolvedprops,
        renderlanes,
      );
    }
	// 类组件
    case classcomponent: {
      const component = workinprogress.type;
      const unresolvedprops = workinprogress.pendingprops;
      const resolvedprops =
        workinprogress.elementtype === component
          ? unresolvedprops
          : resolvedefaultprops(component, unresolvedprops);
      return updateclasscomponent(
        current,
        workinprogress,
        component,
        resolvedprops,
        renderlanes,
      );
    }
	// 根节点
    case hostroot:
      return updatehostroot(current, workinprogress, renderlanes);
	// dom 元素
    case hostcomponent:
      return updatehostcomponent(current, workinprogress, renderlanes);
	// 文本节点
    case hosttext:
      return updatehosttext(current, workinprogress);
	// 省略部分 case 逻辑
  }
  // 省略匹配不上的错误处理
}

当前的 workinprogress 节点为 rootfiber,tag 对应为 hostroot,会调用 updatehostroot 方法。

Fiber 树的构建

rootfiber 的 tag(hostroot)是什么来的?核心代码如下:

export function createhostrootfiber(tag: roottag): fiber {
  // 省略非核心代码
  return createfiber(hostroot, null, null, mode);
}

在创建 rootfiber 节点的时候,直接指定了 tag 参数为 hostroot。

updatehostroot

updatehostroot 的主要逻辑如下:

  1. 调用 reconcilechildren 方法创建 workinprogress.child。
  2. 返回 workinprogress.child。
function updatehostroot(current, workinprogress, renderlanes) {
	// 省略非核心逻辑
  if (root.hydrate && enterhydrationstate(workinprogress)) {
  	// 省略 if 成立的逻辑
  } else {
    reconcilechildren(current, workinprogress, nextchildren, renderlanes);
    resethydrationstate();
  }
  return workinprogress.child;
}

这里有一点需要注意,通过查看源码,你会发现不仅是 updatehostroot 方法,所以的更新方法最终都会调用下面这个方法:

reconcilechildren(current, workinprogress, nextchildren, renderlanes);

只是针对不同的节点类型,会有一些不同的处理,最终殊途同归。

reconcilechildren

reconcilechildren 根据 current 是否为空进行逻辑分发。

export function reconcilechildren(
  current: fiber | null,
  workinprogress: fiber,
  nextchildren: any,
  renderlanes: lanes,
) {
  if (current === null) {
   workinprogress.child = mountchildfibers(
      workinprogress,
      null,
      nextchildren,
      renderlanes,
    );
  } else {
    workinprogress.child = reconcilechildfibers(
      workinprogress,
      current.child,
      nextchildren,
      renderlanes,
    );
  }
}

此时 current 节点不为空,会走 else 逻辑,调用 reconcilechildfibers 创建 workinprogress.child 对象。

reconcilechildfibers

根据 newchild 的类型进行不同的逻辑处理。

function reconcilechildfibers(
    returnfiber: fiber,
    currentfirstchild: fiber | null,
    newchild: any,
    lanes: lanes,
  ): fiber | null {
	// 省略非核心代码
    const isobject = typeof newchild === 'object' && newchild !== null;
    if (isobject) {
      switch (newchild.$$typeof) {
        case react_element_type:
          return placesinglechild(
            reconcilesingleelement(
              returnfiber,
              currentfirstchild,
              newchild,
              lanes,
            ),
          );
	  // 省略其他 case 逻辑
     }
    }
	// 省略非核心代码
    if (isarray(newchild)) {
      return reconcilechildrenarray(
        returnfiber,
        currentfirstchild,
        newchild,
        lanes,
      );
    }
	// 省略非核心代码
  }

newchild 很关键,我们先明确一下 newchild 究竟是什么?通过层层向上寻找,你会在 updatehostroot 方法中发现它其实是最开始传入 render 方法的 app 元素,它在 updatehostroot 中被叫做 nextchildren,到这里我们可以做出这样的猜想,rootfiber 的下一个是 app 节点,并且 app 节点是由 app 元素生成的,下面来看一下 newchild 的结构:

Fiber 树的构建

可以看出 newchild 类型为 object,$$typeof 属性为 react_element_type,所以会调用:

placesinglechild(
  reconcilesingleelement(
    returnfiber,
    currentfirstchild,
    newchild,
    lanes,
  ),
);
reconcilesingleelement

下面继续看 reconcilesingleelement 这个方法:

function reconcilesingleelement(
  returnfiber: fiber,
  currentfirstchild: fiber | null,
  element: reactelement,
  lanes: lanes,
): fiber {
  const key = element.key;
  let child = currentfirstchild;
  
  // 省略 child 不存在的处理逻辑
  if (element.type === react_fragment_type) {
	// 省略 if 成立的处理逻辑
  } else {
    const created = createfiberfromelement(element, returnfiber.mode, lanes);
    created.ref = coerceref(returnfiber, currentfirstchild, element);
    created.return = returnfiber;
    return created;
  }
}

方法的调用比较深,我们先明确一下入参,returnfiber 为 workinprogress 节点,element 其实就是传入的 newchild,也就是 app 元素,所以这个方法的作用为:

  1. 调用 createfiberfromelement 方法根据 app 元素创建 app 节点。
  2. 将新生成的 app 节点的 return 属性指向当前 workinprogress 节点(rootfiber)。此时 fiber 树如下图:

Fiber 树的构建

  1. 返回 app 节点。
placesinglechild

接下来调用 placesinglechild:

function placesinglechild(newfiber: fiber): fiber {
  if (shouldtracksideeffects && newfiber.alternate === null) {
    newfiber.flags = placement;
  }
  return newfiber;
}

入参为之前创建的 app 节点,它的作用为:

  1. 当前的 app 节点打上一个 placement 的 flags,表示新增这个节点。
  2. 返回 app 节点。

之后 app 节点会被一路返回到的 reconcilechildren 方法:

workinprogress.child = reconcilechildfibers(
  workinprogress,
  current.child,
  nextchildren,
  renderlanes,
);

此时 workinprogress 节点的 child 属性会指向 app 节点。此时 fiber 树为:

Fiber 树的构建

beginwork 小结

beginwork 的链路比较长,我们来梳理一下:

  1. 根据 workinprogress.tag 进行逻辑分发,调用形如 updatehostroot、updateclasscomponent 等更新方法。
  2. 所有的更新方法最终都会调用 reconcilechildren,reconcilechildren 根据 current 进行简单的逻辑分发。
  3. 之后会调用 mountchildfibers/reconcilechildfibers 方法,它们的作用是根据 reactelement 对象生成 fiber 节点,并打上相应的 flags,表示这个节点是新增,删除还是更新等等。
  4. 最终返回新创建的 fiber 节点。

简单来说就是创建新的 fiber 字节点,并将其挂载到 fiber 树上,最后返回新创建的子节点。

performunitofwork 小结

下面我们来小结一下 performunitofwork 这个方法,先来回顾一下 workloopsync 方法。

function workloopsync() {
  while (workinprogress !== null) {
    performunitofwork(workinprogress);
  }
}

它会循环执行 performunitofwork,而 performunitofwork,我们已经知道它会通过 beginwork 创建新的 fiber 节点。它还有另外一个作用,那就是把 workinprogress 更新为新创建的 fiber 节点,相关逻辑如下:

// 省略非核心代码
// beginwork 返回新创建的 fiber 节点并赋值给 next
next = beginwork(current, unitofwork, subtreerenderlanes);
// 省略非核心代码
if (next === null) {
  completeunitofwork(unitofwork);
} else {
  // 若 fiber 节点不为空则将 workinprogress 更新为新创建的 fiber 节点
  workinprogress = next;
}

所以当 performunitofwork 执行完,当前的 workinprogress 都存储着下次要处理的 fiber 节点,为下一次的 workloopsync 做准备。
performunitofwork 作用总结如下:

  1. 通过调用 beginwork 创建新的 fiber 节点,并将其挂载到 fiber 树上
  2. 将 workinprogress 更新为新创建的 fiber 节点。

app 节点的处理

rootfiber 节点处理完成之后,对应的 fiber 树如下:

Fiber 树的构建

接下来 performunitofwork 会开始处理 app 节点。app 节点的处理过程大致与 rootfiber 节点类似,就是调用 beginwork 创建新的子节点,也就是 classname 为 container 的 div 节点,处理完成之后的 fiber 树如下:

Fiber 树的构建

这里有一个很关键的地方需要大家注意。我们先回忆一下对 rootfiber 的处理,针对 rootfiber,我们已经知道在 updatehostroot 中,它会提取出 nextchildren,也就是最初传入 render 方法的 element。
那针对 app 节点,它是如何获取 nextchildren 的呢?先来看下我们的 app 组件:

class app extends react.component {
    render() {
        return (
            <div classname="container">
                <div classname="section">
                    <h1>this is the title.</h1>
                    <p>this is the first paragraph.</p>
                    <p>this is the second paragraph.</p>
                </div>
            </div>
        );
    }
}

我们的 app 是一个 class,react 首先会实例化会它:

Fiber 树的构建

之后会把生成的实例挂在到当前 workinprogress 节点,也就是 app 节点的 statenode 属性上:

Fiber 树的构建

然后在 updateclasscomponent 方法中,会先初始化 instance 为 workinprogress.statenode,之后调用 instance 的 render 方法并赋值给 nextchildren:

Fiber 树的构建

此时的 nextchildren 为下面 jsx 经过 react.createelement 转化后的结果:

<div classname="container">
    <div classname="section">
        <h1>this is the title.</h1>
        <p>this is the first paragraph.</p>
        <p>this is the second paragraph.</p>
    </div>
</div>

接着来看一下 nextchildren 长啥样:

Fiber 树的构建

props.children 存储的是其子节点,它可以是对象也可以是数组。对于 app 节点和第一个 div 节点,它们都只有一个子节点。对于第二个 div 节点,它有三个子节点,分别是 h1、p、p,所以它的 children 为数组。

并且 props 还会保存在新生成的 fiber 节点的 pendingprops 属性上,相关逻辑如下:

export function createfiberfromelement(
  element: reactelement,
  mode: typeofmode,
  lanes: lanes,
): fiber {
  let owner = null;
  const type = element.type;
  const key = element.key;
  const pendingprops = element.props;
  const fiber = createfiberfromtypeandprops(
    type,
    key,
    pendingprops,
    owner,
    mode,
    lanes,
  );
  return fiber;
}
export function createfiberfromtypeandprops(
  type: any, // react$elementtype
  key: null | string,
  pendingprops: any,
  owner: null | fiber,
  mode: typeofmode,
  lanes: lanes,
): fiber {
  // 省略非核心逻辑
  const fiber = createfiber(fibertag, pendingprops, key, mode);
  fiber.elementtype = type;
  fiber.type = resolvedtype;
  fiber.lanes = lanes;
  return fiber;
}

第一个 div 节点的处理

app 节点的 nextchildren 是通过构造实例并调用 app 组件内的 render 方法得到的,那对于第一个 div 节点,它的 nextchildren 是如何获取的呢?
针对 div 节点,它的 tag 为 hostcomponent,所以在 beginwork 中会调用 updatehostcomponent 方法,可以看出 nextchildren 是从当前 workinprogress 节点的 pendingprops 上获取的。

function updatehostcomponent(
  current: fiber | null,
  workinprogress: fiber,
  renderlanes: lanes,
) {
  // 省略非核心逻辑
  const nextprops = workinprogress.pendingprops;
  // 省略非核心逻辑
  let nextchildren = nextprops.children;
  // 省略非核心逻辑
  reconcilechildren(current, workinprogress, nextchildren, renderlanes);
  return workinprogress.child;
}

我们之前说过,在创建新的 fiber 节点时,我们会把下一个子节点元素保存在 pendingprops 中。当下次调用更新方法(形如 updatehostcomponent )时,我们就可以直接从 pendingprops 中获取下一个子元素。
之后的逻辑同上,处理完第一个 div 节点后的 fiber 树如下图:

Fiber 树的构建

第二个 div 节点的处理

我们先看一下第二个 div 节点:

<div classname="section">
  <h1>this is the title.</h1>
  <p>this is the first paragraph.</p>
  <p>this is the second paragraph.</p>
</div>

它比较特殊,有三个字节点,对应的 nextchildren 为

Fiber 树的构建

下面我们来看看 react 是如何处理多节点的情况,首先我们还是会进入 reconcilechildfibers 这个方法:

function reconcilechildfibers(
  returnfiber: fiber,
  currentfirstchild: fiber | null,
  newchild: any,
  lanes: lanes,
): fiber | null {
  
  // 省略非核心代码
  if (isarray(newchild)) {
    return reconcilechildrenarray(
      returnfiber,
      currentfirstchild,
      newchild,
      lanes,
    );
  }
  
  // 省略非核心代码
}

newchild 即是 nextchildren,为数组,会调用 reconcilechildrenarray 这个方法

function reconcilechildrenarray(
  returnfiber: fiber,
  currentfirstchild: fiber | null,
  newchildren: array<*>,
  lanes: lanes,
): fiber | null {
  // 省略非核心逻辑
  let previousnewfiber: fiber | null = null;
  let oldfiber = currentfirstchild;
  // 省略非核心逻辑
  if (oldfiber === null) {
    for (; newidx < newchildren.length; newidx++) {
      const newfiber = createchild(returnfiber, newchildren[newidx], lanes);
      if (newfiber === null) {
        continue;
      }
      lastplacedindex = placechild(newfiber, lastplacedindex, newidx);
      if (previousnewfiber === null) {
        resultingfirstchild = newfiber;
      } else {
        previousnewfiber.sibling = newfiber;
      }
      previousnewfiber = newfiber;
    }
    return resultingfirstchild;
  }
  // 省略非核心逻辑
}

下面来总结一下这个方法:

  1. 遍历所有的子元素,通过 createchild 方法根据子元素创建子节点,并将每个字元素的 return 属性指向父节点。
  2. 用 resultingfirstchild 来标识第一个子元素。
  3. 将子元素用 sibling 相连。

最后我们的 fiber 树就构建完成了,如下图:

Fiber 树的构建