React源码解析-启动

22,699 阅读6分钟

在react中有3种启动方式:

  • ReactDOM.render
  • ReactDOM.unstable_createBlockingRoot
  • ReactDOM.unstable_createRoot

第一种方式,是17版本,常用的方式,也称legacy模式

第二种方式,还在实验中,支持了部分并发模式下的功能

第三种方式,也称并发模式,目前还在实验中,后续将做为默认模式

一. 启动模式使用

我们先来了解下,各个模式如何coding

render

ReactDOM.render(<App/>, document.getElementById("app"), () => {});

unstable_createBlockingRoot

ReactDOM.unstable_createBlockingRoot(document.getElementById("app")).render(<App/>)

注意, render方法不支持callback

unstable_createRoot

ReactDOM.unstable_createRoot(document.getElementById("app")).render(<App/>)

注意, render方法不支持callback

二. ReactDOM.render

render

function render(element, container, callback) {
  // ... 校验container必须是DOM
  
  // ... 
  
  return legacyRenderSubtreeIntoContainer(
    null,
    element,
    container,
    false,
    callback,
  );
}

其中,element对象是jsx转换后的element对象,container是DOM容器,callback为回调函数

legacyRenderSubtreeIntoContainer

function legacyRenderSubtreeIntoContainer(
    parentComponent, 
    children, 
    container, 
    forceHydrate, 
    callback
) {
  // ...
  
  let root: RootType = (container._reactRootContainer: any);
  
  if(!root) {
    // 首次渲染,root不存在,将执行下面逻辑
    root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
      container,
      forceHydrate,
    );
    fiberRoot = root._internalRoot;
    
    if (typeof callback === 'function') {
      const originalCallback = callback;
      callback = function() {
        const instance = getPublicRootInstance(fiberRoot);
        originalCallback.call(instance);
      };
    }
 
    unbatchedUpdates(() => {
      updateContainer(children, fiberRoot, parentComponent, callback);
    });
  }else {
    // ... else分支可以先忽略,我们放到后面章节具体分析
    // 这里还会涉及 fiberRoot
  }
}

我们可以看出,首次渲染,root不存在,将进入创建root阶段。 注意,这里开始涉及Fiber。

本质上,root对象包含 render,unmount函数,以及FiberRoot对象。

这里,有个概念,FiberRoot和RootFiber。 root对象创建的是FiberRoot,他是所有Fiber的根

我们先看legacyCreateRootFromDOMContainer

legacyCreateRootFromDOMContainer

function legacyCreateRootFromDOMContainer(container, forceHydrate) {
  // forceHydrate:是否调和复用
  
  const shouldHydrate = forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
    
  // 服务端渲染,是需要复用元素的
  if(!shouldHydrate) {
    // 客户端渲染场景,根root节点下的元素不需要复用,全部删除
    // ...
  }
  
  return createLegacyRoot(
    container,
    shouldHydrate
      ? {
          hydrate: true,
        }
      : undefined,
  );
}

createLegacyRoot

function createLegacyRoot(container, options) {
  return new ReactDOMBlockingRoot(container, LegacyRoot, options)
}

由于是客户端渲染,options为undefined,其中LegacyRoot为0

ReactDOMBlockingRoot

function ReactDOMBlockingRoot(container, tag, options) {
  this._internalRoot = createRootImpl(container, tag, options);
}

createRootImpl

function createRootImpl(container, tag, options) {
  // ...
  
  const root = createContainer(container, tag, hydrate, hydrationCallbacks);
  // 关联fiber和dom
  markContainerAsRoot(root.current, container);
  // ...
  return root;
}

首次创建,核心createContainer,其他逻辑先跳过...

createContainer的定义,在17版本中,区别createContainer_new和createContainer_old。默认的flags为old

createContainer

function createContainer(containerInfo, tag, hydrate, hydrationCallbacks) {
  return createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks);
}

需要说明的是 containerInfo实际上就是container参数,tag = 0,另外2个参数和服务端渲染有关,先忽略

createFiberRoot

function createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks) {
  const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);
  
  // ...
  
  const uninitializedFiber = createHostRootFiber(tag);
  root.current = uninitializedFiber;
  uninitializedFiber.stateNode = root;
  
  initializeUpdateQueue(uninitializedFiber);

  return root;
}

这里,做了2件事。 第一,实例化fiberRoot对象。第二,fiber对象的current指向首个fiber对象,而stateNode属性又指向fiberRoot

FiberRoot

终于,这里我们看到了fiberRoot,这个是fiber的后续运作的顶层对象,所有的fiber对象,都在他下面运作。

本质上: fiberRoot是一个构造函数,其结构如下:

function FiberRootNode(containerInfo, tag, hydrate) {
  // 渲染模式
  this.tag = tag;
  // 容器节点
  this.containerInfo = containerInfo;
  // 持久更新中会使用
  this.pendingChildren = null;
  // 当前对应的fiber对象,这里将会赋值root fiber
  this.current = null;
  
  // ...
  // 记录已经渲染完成的任务,因为任务后面分优先级,优先完成优先级高的任务
  this.finishedWork = null;
  // 这个是异步render,如suspense的fallback,使用setTimeout实现
  this.timeoutHandle = noTimeout;
  // ...
  // 顶层context对象
  this.context = null;
  // ...
  // 是否需要融合
  this.hydrate = hydrate;
  
  // eventTimes, expirationTimes .. 这些属性将会在调度中使用,这里先忽略
  
  // lane模型相关属性
  // ...这里先忽略
  
  // ...
}

createHostRootFiber

function createHostRootFiber(tag) {
  // tag和mode之间的转换
  
  // ...
  
  return createFiber(HostRoot, null, null, mode);
}

注意,这里tag实际上就是渲染模式,在react中,有3种模式,也是我们开篇提到的,其定义如下:

export type RootTag = 0 | 1 | 2;

export const LegacyRoot = 0;
export const BlockingRoot = 1;
export const ConcurrentRoot = 2;

createFiber

function createFiber(tag, pendingProps, key, mode) {
  return new FiberNode(tag, pendingProps, key, mode);
}

默默吐槽一句:这react调用层级也太多了吧~ -_-||

FiberNode

fiber本质也是个构造函数,其结构如下:

function FiberNode(
  tag: WorkTag,
  pendingProps: mixed,
  key: null | string,
  mode: TypeOfMode,
) {
  // 静态属性
  this.tag = tag;
  this.key = key;
  this.elementType = null;
  this.type = null;
  this.stateNode = null;

  // fiber树构造关联属性
  this.return = null;
  this.child = null;
  this.sibling = null;
  this.index = 0;

  this.ref = null;

  // fiber树update时,所需数据
  this.pendingProps = pendingProps;
  this.memoizedProps = null;
  this.updateQueue = null;
  this.memoizedState = null;
  this.dependencies = null;

  this.mode = mode;

  // 副作用
  this.flags = NoFlags;
  this.subtreeFlags = NoFlags;
  this.deletions = null;

  // 优先级调度相关
  this.lanes = NoLanes;
  this.childLanes = NoLanes;

  // 指向内存中的fiber
  this.alternate = null;
  
}

  • tag属性,在react中有25种定义
export type WorkTag =
  | 0
  | 1
  | 2
  | 3
  | 4
  | 5
  | 6
  | 7
  | 8
  | 9
  | 10
  | 11
  | 12
  | 13
  | 14
  | 15
  | 16
  | 17
  | 18
  | 19
  | 20
  | 21
  | 22
  | 23
  | 24;

export const FunctionComponent = 0;
export const ClassComponent = 1;
export const IndeterminateComponent = 2; // Before we know whether it is function or class
export const HostRoot = 3; // Root of a host tree. Could be nested inside another node.
export const HostPortal = 4; // A subtree. Could be an entry point to a different renderer.
export const HostComponent = 5;
export const HostText = 6;
export const Fragment = 7;
export const Mode = 8;
export const ContextConsumer = 9;
export const ContextProvider = 10;
export const ForwardRef = 11;
export const Profiler = 12;
export const SuspenseComponent = 13;
export const MemoComponent = 14;
export const SimpleMemoComponent = 15;
export const LazyComponent = 16;
export const IncompleteClassComponent = 17;
export const DehydratedFragment = 18;
export const SuspenseListComponent = 19;
export const FundamentalComponent = 20;
export const ScopeComponent = 21;
export const Block = 22;
export const OffscreenComponent = 23;
export const LegacyHiddenComponent = 24;
  • key,即element中的key

  • elementType,一般情况就是key

  • type,一般情况和elementType一致

  • stateNode,与fiber关联的节点

  • return,父节点

  • child,第一个子节点

  • sibling,下一个兄弟节点

  • index,fiber在兄弟节点中的索引

  • ref,这个不用多解释了吧

  • pendingProps,后来传入的props

  • memoizedProps,之前的props

  • updateQueue,更新队列

  • memoizedState,之前的state

  • dependencies,fiber节点依赖,比如context

  • mode,运行模式,比如普通模式,并发模式

  • lanes,fiber的优先级处理

  • childLanes,fiber子节点的优先级处理

  • alternate,之前的fiber

  • ...

到这里,我们已经看到root的数据结构:

root = {
   _internalRoot: FiberRoot,
   render: () => {...},
   unmount: () => {...}
}

updateContainer

react根据fiber树做渲染,涉及优先级,调和,调度等, 这里先忽略,后面章节详细再分析

三. unstable_createBlockingRoot

这种启动方式,支持了小部分并发模式功能。

createBlockingRoot

function createBlockingRoot(container, options) {
  // ...校验
  
  // ...
  
  return new ReactDOMBlockingRoot(container, BlockingRoot, options);
}

这里和ReactDOM.render类型,只是tag模式不一样而已

ReactDOMBlockingRoot

function ReactDOMBlockingRoot(container, tag, options) {
  this._internalRoot = createRootImpl(container, tag, options);
}

这里,又走到ReactDOM.render方法里了,这里的tag值是1,不是render方法中的 0。 tag将在fiber中体现,后续文章中,我们会细聊区别。

ReactDOMBlockingRoot.render

ReactDOMBlockingRoot.prototype.render = () => {
  // ...
  updateContainer(children, root, null, null)
}

不难看出,此种模式下,直接更新容器了

四. unstable_createRoot

createRoot

function createRoot(container, options) {
  // ...校验 
  
  // ...
  
  return new ReactDOMRoot(container, options);
}

ReactDOMRoot

function ReactDOMRoot(container: Container, options: void | RootOptions) {
  this._internalRoot = createRootImpl(container, ConcurrentRoot, options);
}

此方法,也走到了createRootImpl方法调用,此时模式为3,也就是并发模式,生成fiber流程是一致的,只是fiber的tag值不一样。

并发模式,其核心在于:可中断的异步渲染

这也是react团队,历时两年重构使用fiber的目标。并发模式也将在react18中大放异彩

五. 总结

react的启动模式有3种,分别是普通模式,小部分并发模式,并发模式。尽管3种方式启动方法调用不一样,但最终都将调用createRootImpl。 其区别在在启动阶段,区分有2。其一: fiber树tag不同,其二:render定义不同。 后两者,执行render时,直接执行updateContainer