react render做了些什么

178 阅读3分钟

ReactDOM.render

首先react通过协调算法(reconciliation)去比较更新前后的VDOM,从而找到需要更新的最小操作,减少了浏览器多次操作DOM的成本。但是,由于使用递归的方式来遍历组件树,当组件树越来越大,递归遍历的成本就越高。这样,由于持续占用主线程,像布局、动画等任务无法立即得到处理,就会出现丢帧的现象。所以,为不同类型的任务赋予优先级,同时支持任务的暂停、中止与恢复,是非常有必要的。

为了解决上面存在的问题,React团队给出了React Fiber算法以及fiber tree数据结构(基于单链表的树结构),而ReactDOM.render方法就是实现React Fiber算法以及构建fiber tree的核心API。

render()方法定义如下:

ReactDOM.render(element, container[, callback])

这里重点从源码层面讲解下ReactDOM.render是如何构建fiber tree的。

ReactDOM.render实际调用了legacyRenderSubtreeIntoContainer方法,调用过程以及传参如下:

ReactDOM = {
  render(element, container, callback) {
    return legacyRenderSubtreeIntoContainer(
      null,
      element,
      container,
      false,
      callback
    );
  },
};

其中的elementcontainer我们都很熟悉了,而callback是用来渲染完成后需要执行的回调函数。再来看看该方法的定义。

function legacyRenderSubtreeIntoContainer(
  parentComponent,
  children,
  container,
  forceHydrate,
  callback
) {
  let root = container._reactRootContainer;
  let fiberRoot;
  // 初次渲染
  if (!root) {
    // 初始化挂载,获得React根容器对象
    root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
      container,
      forceHydrate
    );
    fiberRoot = root._internalRoot;

    // 初始化安装不需要批量更新,需要尽快完成
    unbatchedUpdates(() => {
      updateContainer(children, fiberRoot, parentComponent, callback);
    });
  } else {
    fiberRoot = root._internalRoot;

    updateContainer(children, fiberRoot, parentComponent, callback);
  }
  return getPublicRootInstance(fiberRoot);
}

上面是简化后的源码。先来看传参,因为是挂载root,所以parentComponent设置为null。另外一个参数forceHydrate代表是否是服务端渲染,因为调用的render()方法为客服端渲染,所以默认为false。另外callback使用少,所以关于它的处理过程就省略了。

因为是首次挂载,所以rootcontainer._reactRootContainer获取不到值,就会创建FiberRoot对象。在FiberRoot对象创建过程中考虑到了服务端渲染的情况,并且函数之间相互调用非常多,所以这里直接展示其最终调用的核心方法。

// 创建fiberRoot和rootFiber并相互引用
function createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks) {
  const root = new FiberRootNode(containerInfo, tag, hydrate);
  if (enableSuspenseCallback) {
    root.hydrationCallbacks = hydrationCallbacks;
  }

  // 创建fiber tree的根节点,即rootFiber
  const uninitializedFiber = createHostRootFiber(tag);
  root.current = uninitializedFiber;
  uninitializedFiber.stateNode = root;

  initializeUpdateQueue(uninitializedFiber);

  return root;
}
}

在该方法中containerInfo就是root节点,而tagFiberRoot节点的标记,这里为LegacyRoot。另外两个参数和服务端渲染有关。这里使用FiberRootNode方法创建了FiberRoot对象,并使用createHostRootFiber方法创建RootFiber对象,使FiberRoot中的current指向RootFiberRootFiberstateNode指向FiberRoot,形成相互引用。

下面的两个构造函数是展现出了fiberRoot以及rootFiber的部分重要的属性。

FiberRootNode部分属性:

function FiberRootNode(containerInfo, tag, hydrate) {
  // 用于标记fiberRoot的类型
  this.tag = tag;
  // 指向当前激活的与之对应的rootFiber节点
  this.current = null;
  // 和fiberRoot关联的DOM容器的相关信息
  this.containerInfo = containerInfo;
  // 当前的fiberRoot是否处于hydrate模式
  this.hydrate = hydrate;
  // 每个fiberRoot实例上都只会维护一个任务,该任务保存在callbackNode属性中
  this.callbackNode = null;
  // 当前任务的优先级
  this.callbackPriority = NoPriority;
}

Fiber Node构造函数的部分属性:

function FiberNode(tag, pendingProps, key, mode) {
  // rootFiber指向fiberRoot,child fiber指向对应的组件实例
  this.stateNode = null;
  // return属性始终指向父节点
  this.return = null;
  // child属性始终指向第一个子节点
  this.child = null;
  // sibling属性始终指向第一个兄弟节点
  this.sibling = null;
  // 表示更新队列,例如在常见的setState操作中,会将需要更新的数据存放到updateQueue队列中用于后续调度
  this.updateQueue = null;
  // 表示当前更新任务的过期时间,即在该时间之后更新任务将会被完成
  this.expirationTime = NoWork;
}

最终生成的fiber tree结构示意图如下:

6633763-a3a4d07d32604420.png