🍂 React Fiber树“头部”的构建

253 阅读3分钟

著有《React 源码》《React 用到的一些算法》《javascript地月星》等多个专栏。欢迎关注。

文章不好写,要是有帮助别忘了点赞,收藏,评论 ~你的鼓励是我继续挖干货的动力🔥。

另外,本文为原创内容,商业转载请联系作者获得授权,非商业转载需注明出处,感谢理解~

前言

这一篇介绍Fiber树 App之前的“头部”节点 FiberRootNode HostRootFiber的构建过程,然后梳理名称很像FiberRootNode HostRootFiber HostRoot功能很像(其实完全不一样)的stateNode containerInfo

总流程

创建FiberRootNodeHostRootFiber->创建workInProgress->创建App和后续节点。

初次的挂载.png

阶段一:遍历Fiber树前

  • createRoot()->createFiberRoot(),创建FiberRootNode和HostRootFiber。
createRoot(document.getElementById('root'))

//这个函数里面创建new FiberRootNode和创建HostRootFiber(createHostRootFiber)
function createFiberRoot(containerInfo, tag, hydrate, initialChildren, hydrationCallbacks, isStrictMode, concurrentUpdatesByDefaultOverride, 
identifierPrefix, onRecoverableError, transitionCallbacks) {
  //1.创建FiberRootNode
  var root = new FiberRootNode(containerInfo, tag, hydrate, identifierPrefix, onRecoverableError);

  //2.创建HostRootFiber
  var uninitializedFiber = createHostRootFiber(tag, isStrictMode);
  //3.FiberRootNode.current = HostRootFiber
  root.current = uninitializedFiber;
  //4.HostRootFiber.stateNode = FiberRootNode
  uninitializedFiber.stateNode = root;

  {
    var _initialState = {
      element: initialChildren,
      isDehydrated: hydrate,
      cache: null,
      // not enabled yet
      transitions: null,
      pendingSuspenseBoundaries: null
    };
    uninitializedFiber.memoizedState = _initialState;
  }

  initializeUpdateQueue(uninitializedFiber);
  return root;
}
  • createRoot().render()->prepareFreshStack()->createWorkInProgress()创建第一个workInProgress。
createRoot(document.getElementById('root')).render(<App />)


function prepareFreshStack(root, lanes) {
  ...
  // root是FiberRootNode    root.current是HostRootFiber
  var rootWorkInProgress = createWorkInProgress(root.current, null);
  ...
}
function createWorkInProgress(current, pendingProps) {
  // 1.此时是null
  var workInProgress = current.alternate;

  if (workInProgress === null) {
    // 2.创建workInprogress
    workInProgress = createFiber(current.tag, pendingProps, current.key, current.mode);
    workInProgress.elementType = current.elementType;
    workInProgress.type = current.type;
    //3. current.sateNode 是HostRootFiber.stateNode是FiberRootNode
    workInProgress.stateNode = current.stateNode;

    {
      // DEV-only fields
      workInProgress._debugSource = current._debugSource;
      workInProgress._debugOwner = current._debugOwner;
      workInProgress._debugHookTypes = current._debugHookTypes;
    }
    // 4. cur和wip相互指向
    workInProgress.alternate = current;
    current.alternate = workInProgress;
  } else {
    ...
  } 
   ...
  }

  return workInProgress;
} 

阶段二:正式遍历Fiber树

prepareFreshStack后,遍历Fiber树,创建App节点以及后续的节点。

  • updateHostRoot的参数current和workInProgress是上面创建的HostRootFiber。
  • reconcileChildren,创建HostRootFiber的子节点App。
  • 首次挂载的时候,App节点还没有current。但是HostRootFiber有current和workInProgress。
    在源码中经常使用if(current === null){}来判断是挂载节点还是更新节点。
function renderRootSync(){
  ...
  if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
    ...
    // root是fiberRootNode, tag=1
    prepareFreshStack(root, lanes)//创建HostRootFiber的wip,cur和wip相互指向
  }
  ...
  // prepareFreshStack后马上进入到do-while循环遍历fiber树
  do {
    try {
      workLoopSync();  //这一步创建tag=3的子fiber,因为进入到beginWork-updateHostRoot-reconcileChildren
      break;
    } catch (thrownValue) {
      handleError(root, thrownValue);
    }
  } while (true);
  ...
}
function updateHostRoot(current, workInProgress, renderLanes) {
  ...
  reconcileChildren(current, workInProgress, nextChildren, renderLanes);
}
function reconcileChildren(current, workInProgress, nextChildren, renderLanes) {
  if (current === null) {
    workInProgress.child = mountChildFibers(workInProgress, null, nextChildren, renderLanes);
  } else {
    // current是HostRootFiber,workInProgress是HostRootFiber,创建App
    workInProgress.child = reconcileChildFibers(workInProgress, current.child, nextChildren, renderLanes);
  }
}

梳理FiberRootNode HostRootFiber HostRoot

  • FiberRootNode不是Fiber节点,tag有两种类型:
var LegacyRoot = 0; //传统的同步渲染模式 React 16 之前的版本中唯一的模式 更新都是同步的、不可中断的
var ConcurrentRoot = 1; //并发渲染模式 React 18 引入的全新模式

Fiber树切换的时候,切换的是FiberRootNodecurrent,Fiber树切换:

function commitRootImpl(root, recoverableErrors, transitions, renderPriorityLevel) {
  ...
  if (subtreeHasEffects || rootHasEffect) {
    ...
    root.current = finishedWork; 
  }else{
    root.current = finishedWork; 
    ...
  }
  ...
}
  • HostRootFiber是一个Fiber节点,是HostRoot的Fiber节点。
  • HostRoot是一个tag,表示Fiber节点的类型(tag)。
    HostRootFiber是一种Fiber节点,tag有:
var FunctionComponent = 0;
var ClassComponent = 1;
var IndeterminateComponent = 2; // Before we know whether it is function or class
var HostRoot = 3; // Root of a host tree. Could be nested inside another node.
var HostPortal = 4; // A subtree. Could be an entry point to a different renderer.
...

HostRootFiber.tag = HostRoot = 3。

结语

Fiber树不是“一体成型”的,是由头部 + App及以下节点拼接而成。真正的遍历Fiber树是从App开始的。
FiberRootNode HostRootFiber HostRoot有点复杂的,不熟悉的话容易搞混。
除此之外,还有stateNode和containerInfo也容易搞乱,下篇继续梳理stateNode和containerInfo

相关阅读:React DOM树的构建原理和算法