
2025-05-06 日报 Day178

今日的鸡汤
几代青年,皆以梦为马,不断探索中国未来。而今天的我们,则有机会把整个世界作为想象的行动的空间,去一展拳脚、实现抱负。
今日学习内容
1、https://react.iamkasong.com/
今日笔记
Fiber架构的工作原理:
1、Fiber节点可以保存对应的DOM节点。Fiber节点构成的Fiber树就对应DOM树。那么如何更新DOM呢?这需要用到被称为“双缓存”的技术。
2、当我们用canvas绘制动画,每一帧绘制前都会调用ctx.clearRect清除上一帧的画面。如果当前帧画面计算量比较大,导致清除上一帧画面到绘制当前帧画面之间有较长间隙,就会出现白屏。
为了解决这个问题,我们可以在内存中绘制当前帧动画,绘制完毕后直接用当前帧替换上一帧画面,由于省去了两帧替换间的计算时间,不会出现从白屏到出现画面的闪烁情况。
这种在内存中构建并直接替换的技术叫做双缓存。
React使用“双缓存”来完成Fiber树的构建与替换——对应着DOM树的创建与更新。
3、双缓存Fiber树:
在React中最多会同时存在两棵Fiber树。当前屏幕上显示内容对应的Fiber树称为current Fiber树,正在内存中构建的Fiber树称为workInProgress Fiber树。
current Fiber树中的Fiber节点被称为current fiber,workInProgress Fiber树中的Fiber节点被称为workInProgress fiber,他们通过alternate属性连接。
1 | currentFiber.alternate === workInProgressFiber; |
React应用的根节点通过使current指针在不同Fiber树的rootFiber间切换来完成current Fiber树指向的切换。
即当workInProgress Fiber树构建完成交给Renderer渲染在页面上后,应用根节点的current指针指向workInProgress Fiber树,此时workInProgress Fiber树就变为current Fiber树。
每次状态更新都会产生新的workInProgress Fiber树,通过current与workInProgress的替换,完成DOM更新。
接下来我们以具体例子讲解mount时、update时的构建/替换流程。
mount:
1 | function App() { |
首次执行ReactDOM.render会创建fiberRootNode(源码中叫fiberRoot)和rootFiber。其中fiberRootNode是整个应用的根节点,rootFiber是
在构建workInProgress Fiber树时会尝试复用current Fiber树中已有的Fiber节点内的属性,在首屏渲染时只有rootFiber存在对应的current fiber(即rootFiber.alternate)。
图中右侧已构建完的workInProgress Fiber树在commit阶段渲染到页面。
此时DOM
更新为右侧树对应的样子。fiberRootNode
的current
指针指向workInProgress Fiber树
使其变为current Fiber 树
。
Update:
接下来我们点击
p节点
触发状态改变,这会开启一次新的render阶段
并构建一棵新的workInProgress Fiber 树
。
和mount
时一样,workInProgress fiber
的创建可以复用current Fiber树
对应的节点数据。
这个决定是否复用的过程就是 Diff 算法,后面章节会详细讲解
2、workInProgress Fiber 树
在render阶段
完成构建后进入commit阶段
渲染到页面上。渲染完毕后,workInProgress Fiber 树
变为current Fiber 树
。
1 | // performSyncWorkOnRoot会调用该方法 |
可以看到,他们唯一的区别是是否调用shouldYield。如果当前浏览器帧没有剩余时间,shouldYield会中止循环,直到浏览器有空闲时间后再继续遍历。
workInProgress代表当前已创建的workInProgress fiber。
performUnitOfWork方法会创建下一个Fiber节点并赋值给workInProgress,并将workInProgress与已创建的Fiber节点连接起来构成Fiber树。
performUnitOfWork的工作可以分为两部分:“递”和“归”。
“递”阶段: 首先从rootFiber开始向下深度优先遍历。为遍历到的每个Fiber节点调用beginWork 方法
该方法会根据传入的Fiber节点创建子Fiber节点,并将这两个Fiber节点连接起来。
当遍历到叶子节点(即没有子组件的组件)时就会进入“归”阶段。
“归”阶段:在“归”阶段会调用completeWork处理Fiber节点。
当某个Fiber节点执行完completeWork,如果其存在兄弟Fiber节点(即fiber.sibling !== null),会进入其兄弟Fiber的“递”阶段。
如果不存在兄弟Fiber,会进入父级Fiber的“归”阶段。
“递”和“归”阶段会交错执行直到“归”到rootFiber。至此,render阶段的工作就结束了。
例子:
1 | function App() { |
对应的Fiber树结构:
render阶段
会依次执行:
1 | 1. rootFiber beginWork |
注意
之所以没有 “KaSong” Fiber 的 beginWork/completeWork,是因为作为一种性能优化手段,针对只有单一文本子节点的Fiber
,React
会特殊处理。
4、beginWork:render阶段的工作可以分为“递”阶段和“归”阶段。其中“递”阶段会执行beginWork,“归”阶段会执行completeWork。
1 | function beginWork(current: Fiber | null, workInProgress: Fiber, renderLanes: Lanes): Fiber | null { |
current:当前组件对应的Fiber节点在上一次更新时的Fiber节点,即workInProgress.alternate
workInProgress:当前组件对应的Fiber节点
renderLanes:优先级相关,在讲解Scheduler时再讲解
从双缓存机制一节我们知道,除rootFiber以外, 组件mount时,由于是首次渲染,是不存在当前组件对应的Fiber节点在上一次更新时的Fiber节点,即mount时current === null。
组件update时,由于之前已经mount过,所以current !== null。
所以我们可以通过current === null ?来区分组件是处于mount还是update。
基于此原因,beginWork的工作可以分为两部分:
update时:如果current存在,在满足一定条件时可以复用current节点,这样就能克隆current.child作为workInProgress.child,而不需要新建workInProgress.child。
mount时:除fiberRootNode以外,current === null。会根据fiber.tag不同,创建不同类型的子Fiber节点
1 | function beginWork( |
update时:
我们可以看到,满足如下情况时didReceiveUpdate === false(即可以直接复用前一次更新的子Fiber,不需要新建子Fiber)
oldProps === newProps && workInProgress.type === current.type,即props与fiber.type不变
!includesSomeLane(renderLanes, updateLanes),即当前Fiber节点优先级不够,会在讲解Scheduler时介绍
1 | if (current !== null) { |
mount时:
当不满足优化路径时,我们就进入第二部分,新建子Fiber。
我们可以看到,根据fiber.tag不同,进入不同类型Fiber的创建逻辑。
reconcileChildren:从该函数名就能看出这是Reconciler模块的核心部分。那么他究竟做了什么呢?
对于mount的组件,他会创建新的子Fiber节点
对于update的组件,他会将当前组件与该组件在上次更新时对应的Fiber节点比较(也就是俗称的Diff算法),将比较的结果生成新Fiber节点
1 | export function reconcileChildren( |
从代码可以看出,和beginWork一样,他也是通过current === null ?区分mount与update。
不论走哪个逻辑,最终他会生成新的子Fiber节点并赋值给workInProgress.child,作为本次beginWork返回值,并作为下次performUnitOfWork执行时workInProgress的传参。
注意
值得一提的是,mountChildFibers与reconcileChildFibers这两个方法的逻辑基本一致。唯一的区别是:reconcileChildFibers会为生成的Fiber节点带上effectTag属性,而mountChildFibers不会。
effectTag:
我们知道,render阶段的工作是在内存中进行,当工作结束后会通知Renderer需要执行的DOM操作。要执行DOM操作的具体类型就保存在fiber.effectTag中。
1 | // DOM需要插入到页面中 |
beginWork
流程图
