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方法,最终做如下判断:
在使用diff算法判断DOM不可复用的时候,需要将上次的fiber节点打上DELETE的effectTag。然后新生成一个fiber节点,打上新增effectTag。同理,若上次更新时的fiber节点不存在对应的DOM节点,那就直接新生成一个,打上新增的effectTag。
最终由React.Renderer渲染器去更新有EffectTag的节点。这就完成了一次重渲染。
整体流程图如下所示:
beginWork
completeWork: