beginWork和completeWork流程

213 阅读2分钟

1. 使用CRA创建的React项目的index.jsx文件是这样的

import React from "react";
import ReactDOM from "react-dom";
import App from "./App";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

这里将id为root的div作为React组件挂载的根节点

root.render本质上是一个web端渲染器,功能是将虚拟DOM渲染为真实DOM并挂载在页面上。

2. 初次加载页面的时候

JSX -> Babel -> React.createElement -> ReactElement -> virtualdom -> ReactDom.Render -> 真实DOM

初次渲染的时候没有currentTree,这个时候就是从根节点一层一层的去向下构建fiber节点。执行beginWork和completeWork这两个方法。当completeWork返回到根节点的时候,就说明这个workInProgress树构建完成,此时fiberRootNode的current指针会指向workInProgress树,将其变为CurrentTree。然后ReactDom.Render会将其渲染到页面上,完成页面初次渲染。

第一次是没有currentTree的所以也就不存在复用fiber节点的逻辑,都是生成新的节点。

3. 重渲染的时候

3.1. rerender的时候会有双缓冲模式去尽可能的复用Fiber节点

在rerender的时候,会重新执行函数组件,或者执行Class组件的render方法。在重新构建workInProgress树的时候,如果当前节点存在current节点,那么会考虑复用current节点,即直接让workInProgress.children = current.children,这样就不用重新构建的workInProgress树了。(rerender触发:由状态改变引发,由父组件重渲染引发)

复用current节点的条件是 didReceiveUpdate === false

if (current !== null) {
  const oldProps = current.memoizedProps;
  const newProps = workInProgress.pendingProps;

  if (
    oldProps !== newProps ||
    hasLegacyContextChanged() ||
    (__DEV__ ? workInProgress.type !== current.type : false)
  ) {
    didReceiveUpdate = true;
  } else if (!includesSomeLane(renderLanes, updateLanes)) {
    didReceiveUpdate = false;
    switch (
      workInProgress.tag
      // 省略处理
    ) {
    }
    return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
  } else {
    didReceiveUpdate = false;
  }
} else {
  didReceiveUpdate = false;
}

需要注意的是,在复用前需要先判断当前子树是否需要检查更新,若需检查更新则调用cloneChildFibers方法。

当然rerender还是需要走完beginWork和completeWork两个过程,上述过程只是加速了beginWork的速度。在执行完CompleteWork后还是需要交给ReactDom.Render去渲染的。

3.2. 若达不到复用的条件,那么就需要重新创建子fiber节点,这个操作最终会调用reconcileChildren方法

在reconcileChildren中会根据此次渲染是update还是mount来判断是调用reconcileChildFibers还是调用mountChildFibers(根据 current === null 来判断是否为mount)

mount就根据workInprogress.tag(组件类型)调用mountChildFibers方法创建新子fiber节点

update同理,根据workInprogress.tag(组件类型)调用reconcileChildFibers方法,最终做如下判断:

image.png

在使用diff算法判断DOM不可复用的时候,需要将上次的fiber节点打上DELETE的effectTag。然后新生成一个fiber节点,打上新增effectTag。同理,若上次更新时的fiber节点不存在对应的DOM节点,那就直接新生成一个,打上新增的effectTag。

最终由React.Renderer渲染器去更新有EffectTag的节点。这就完成了一次重渲染。

整体流程图如下所示:

beginWork image.png

completeWork:

image.png