从源码角度理解 React Render
Render阶段主要创建 Fiber节点,生成Fiber树,在更新阶段进行操作,diff产生具有副作用的effectList
主要有以下重要的步骤:
- beginWork阶段
- completeWork阶段
在详细阐述render阶段之前,先看下整个render阶段的流程图;
render阶段主要分为beginWork
和completeWork
两个阶段,也可以称之为递阶段和归阶段。
我们知道,整个react
应用通过JSX
解析成了一颗visual DOM
树,而要去遍历树结构,去处理每一个节点,则需要使用深度优先遍历(DFS),在react
源码中,也是使用的这种方法,不过改用了循环的方式,因为这样可以使执行中断。
render 阶段开始于ReactDOM.render()
,在这个阶段创建了FiberRoot
节点和rootFiber
节点,也就是当前应用的根节点,如果存在多应用的话,对应的FiberRoot
节点也会存在多个,但是rootFiber
每个应用只有一个。
// 创建FiberRoot节点export function createFiberRoot(containerInfo: any,tag: RootTag,hydrate: boolean,hydrationCallbacks: null | SuspenseHydrationCallbacks,): FiberRoot {// 创建FiberRoot 阶段const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);if (enableSuspenseCallback) {root.hydrationCallbacks = hydrationCallbacks;}// 创建RootFiber tag = 2const uninitializedFiber = createHostRootFiber(tag);// 初始化 current = rootFiberroot.current = uninitializedFiber;// rootFiber.steteNode = fiberRootuninitializedFiber.stateNode = root;// 初始化更新队列initializeUpdateQueue(uninitializedFiber);return root;}
React.render()
方法是在react-dom
包中,但是真正协调过程(包括创建``fiberRoot节点以及
rootFiber )是在
react-reconciler包中,因此协调算法也可以脱离
react`而执行。
接下来进入Render阶段的核心,开始于performSyncWorkOnRoot
或者performConcurrentWorkOnRoot
,同步渲染或者异步渲染(可中断异步渲染)。
如果要开启异步渲染,则需要更改ReactDOM
的渲染方法
ReactDOM.render(<App />, rootEl); // 同步渲染ReactDOM.unstable_createRoot(rootEl).render(<App />); // 异步渲染 目前版本处于试验阶段 待18v正式发布
如下,如果使用异步渲染,则需要执行shouldYield
调度方法,每处理完一个Fiber
节点都需要浏览器当前帧是都还有剩余的空余时间,如果没有,则中断执行,将控制权交还给浏览器。直到下一帧有空余时间。
// 在Fiber上同步执行function workLoopSync() {// Already timed out, so perform work without checking if we need to yield.while (workInProgress !== null) {performUnitOfWork(workInProgress);}}// 在Fiber上并发执行function workLoopConcurrent() {while (workInProgress !== null && !shouldYield()) {performUnitOfWork(workInProgress);}}
这里的workInProgress
为当前已经都构建的workInProgress fiber
节点。
performUnitOfWork
主要创建next Fiber
节点,完成之后赋值给workInProgress
,将workInProgress
节点和已创建的Fiber
节点通过child
或者sibling
连接起来,构成整个Fiber
树。
beginWork 阶段
从上图可以看出,beginWork
阶段主要有两个流程:
- 首次渲染
- 更新渲染
首次渲染 - begin
首次渲染主要是首次构建 Fiber
节点以及 Fiber树
,构建结束fiberRoot.current
会指向当前构建完成的Fiber
树,也就是当前页面上展示的DOM
结构。
更新渲染 - begin
当发生页面局部更新后,就会重新构建workInProgress Fiber
树,此时workInProgress Fiber
树会复用rootFiber
节点,然后遍历其子Fiber
节点,这里会通过workInProgress.alternate
获取其oldFiber
节点,然后与JSX
对象(虚拟对象)进行diff
算法,如果能够复用,则复用其Fiber
节点,不能复用,则创建对应类型的新Fiber
节点。直到workInProgress Fiber
树构建完成。
详细的过程看后面的章节。
completeWork阶段
从上图可以看出,completeWork
阶段也有两个流程:
- 首次渲染
- 更新渲染
首次渲染 - complete
在completeWork
中,首次渲染会在之前创建Fiber
节点的基础上创建当前类型的真实DOM
,然后挂载到fiber.statenode
上。最后为会rootFiber
节点打上snapshot= 256
的flag
。
在处理下一个Fiber
节点之前,会判断当前节点有没有兄弟节点,如果有兄弟节点,则重新走beginWork
流程,走完之后从新开始的最后一个节点开始走completeWork
流程。
更新渲染 - complete
更新之后,beginWork
阶段主要进行协调过程(diff)
,然后创建新的Fiber
节点;
在completeWork
阶段中,会将新节点
和老节点
进行diffProperties
(属性对比),然后会生成一个updateQueue
,updateQueue
中以类似于(键值对)的形式存储新的值,比如['title', 1]
,第一个是属性名称,第二个是属性值。
除了构建workInProgress Fiber
树之外,在completeWork
中会为有更新的Fiber
节点打上更新的flag
,然后根据flag
生成effectList
(副作用链表),这个链表的起始会传递到rootFiber.firstEffect
,如下图:
蓝色背景的节点为有更新的节点,右侧生成的就是这次render阶段完成的effectList
。
详细的过程看后面的章节。