狗教我React——原理篇之Fiber和Fiber双缓冲修勾课堂开课了!在【狗教我React——原理篇之React整体架
前置文章:
-----读懂这一篇需要对React整体架构和渲染流程有大致的概念😊-----
Fiber 节点是 Fiber 架构的核心概念之一,它是一种虚拟DOM的实现方式。
Fiber本质上是一个对象, 使用了链表结构。
双重缓冲是一种渲染优化技术,其使用两个Fiber树来管理渲染,即当前树 current tree和工作树 work-in-progress tree。当前树代表屏幕上当前显示的内容,而工作树用于准备下一次的渲染更新,用以实现平滑的更新。
本篇将详细介绍Fiber以及Fiber架构的工作原理,即如何使用“双缓存”来完成Fiber树的构建与替换。
Fiber的含义
前面反复提到,与React16之前的栈式架构相比,Fiber架构中的更新工作是可以中断的循环过程。
fiber 译为“纤维”,React的Fiber架构借鉴了Fiber作为轻量级、可调度执行单元的概念,将其应用于组件的渲染和更新过程中。
实际上,Fiber
包含三层含义:
- fiber架构
- 静态的数据结构
- 动态的工作单元
fiber架构
React16
之前的Reconciler
采用递归的方式执行,数据保存在递归调用栈中,所以被称为stack Reconciler
。React16
的Reconciler
基于Fiber节点
实现,被称为Fiber Reconciler
,各个FiberNode之间通过链表的形式串联起来。
看一下简化版源码:
function FiberNode(tag, pendingProps,key, mode, ) {
//...
// Fiber树结构:周围的 Fiber Node 通过链表的形式进行关联
this.return = null; // 上一级节点
this.child = null; // 第一个子节点
this.sibling = null; // 下一个同级节点
this.index = 0; // 在上一级节点中的索引
//...
}
静态的数据结构
作为静态的数据结构来说,每个Fiber节点
对应一个React element
,保存了该组件的类型(函数组件/类组件/原生组件...)、对应的DOM节点等信息。
function FiberNode(tag, pendingProps,key, mode, ) {
//...
// 实例属性:
// 节点类型标记 Function/Class/Host...
this.tag = tag;
// key属性
this.key = key;
// 组件的元素类型,大部分情况同type,某些情况不同,比如FunctionComponent使用React.memo包裹
this.elementType = null;
// 实际的 JavaScript 对象类型。对于 FunctionComponent,指函数本身,对于ClassComponent,指class,对于HostComponent,指DOM节点tagName
this.type = null;
// 节点对应的真实DOM节点
this.stateNode = null;
//...
}
动态的工作单元
作为动态的工作单元来说,每个Fiber节点
保存了本次更新中该组件改变的状态、要执行的工作(需要被删除/被插入页面中/被更新...)。
// Props和State 改变相关信息
this.pendingProps = pendingProps; // 当前待处理的props
this.memoizedProps = null; // 上次渲染完成,已应用到组件的props
this.updateQueue = null; // 更新队列,用于存储状态更新和回调
this.memoizedState = null; // 上次渲染完成后的state,即组件的当前状态
this.dependencies = null; // 依赖列表,用于追踪副作用
this.mode = mode; // Fiber的模式
// Effects 副作用
this.flags = NoFlags; // Fiber的标志位,表示Fiber的生命周期状态
this.subtreeFlags = NoFlags; // 子树的标志位
this.deletions = null; // 待删除的子Fiber列表
// 优先级调度
this.lanes = NoLanes; // 当前Fiber的优先级
this.childLanes = NoLanes; // 子Fiber的优先级
Fiber双缓冲
对于fiber,我们已经有一些了解了。那么fiber节点构成的fiber树和页面上的DOM树有什么关系呢?我们经常看到的fiber双缓冲是什么?
双缓冲的概念
双缓冲(Double Buffering)是一种在计算机图形学和用户界面设计中常用的技术,简单来说,就是通过将绘制和显示过程分离,改善图像渲染的流畅性和视觉效果。
如上图,普通的绘图方式就像是直接在电脑屏幕上画图,用户可以看到绘图的每一个步骤,这样不太优雅。
双缓冲就类似于首先在内存上创建一个“虚拟屏幕”,所有的图形绘制工作都在虚拟屏幕上完成。这个虚拟屏幕就像是一个幕后的画布,绘图或称首先在这个虚拟屏幕上进行,用户看不到绘图的过程。
当虚拟屏幕上的图形绘制完成时,绘图程序会迅速将整个画面一次性拷贝到电脑屏幕上,替换掉之前的画面,这个拷贝过程是瞬间完成的。
这样,用户在屏幕上看到的图像始终都是完整的。
React中的双缓冲fiber树
在 React 源码中,很多方法都需要接收两颗 FiberTree:
function cloneChildFibers(current, workInProgress){
// ...
}
current
是当前屏幕上显示内容对应的 FiberNode,workInProgress
指的是正在内存中构建的 FiberNode。
两个 FiberNode 会通过 alternate
属性相互指向:
current.alternate = workInProgress;
workInProgress.alternate = current;
每次状态更新都会产生新的workInProgress Fiber Tree
,通过current
与workInProgress
的替换,完成DOM
更新。
可以从首次渲染(mount)
和更新(update)
这两个阶段来看一下 FiberTree 的构建/替换流程。
首次渲染(mount)
首先我们先了解一下几个概念:
-
fiberRootNode:整个应用的根节点,
fiberRootNode
的current
会指向当前页面上已渲染内容对应Fiber树
,即current Fiber Tree
。 -
hostRootFiber: 它是一个具体的Fiber节点实例,具有Fiber节点的所有属性和方法。通常包含指向宿主环境(如DOM)的引用,并且负责协调React组件与宿主环境之间的交互。
-
rootFiber:一个通用术语,用来指代Fiber树的根节点,包括
HostRootFiber
,其他类型的根Fiber等。 -
workInProgress Fiber Tree: 内存中构建的树,简写WIP FiberTree。
-
current Fiber Tree: 页面显示的树。
// 示例
function App() {
const [num, setNum] = useState(0);
return (
<p onClick={() => setNum(prevNum => prevNum + 1)}>{num}</p>
);
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<App />
);
流程一:
当执行 ReactDOM.createRoot 时:
// ReactFiberRoot.js 伪代码
function createFiberRoot(){
//...
// 创建 FiberRootNode 实例
const root = new FiberRootNode(/* 参数 */);
// 创建 HostRootFiber 实例
const uninitializedFiber = createHostRootFiber(/* 参数 */);
// 将 HostRootFiber 设置为 FiberRoot 的 current 属性
root.current = uninitializedFiber;
// ...
return root;
}
此时会创建 fiberRootNode 和 hostRootFiber,fiberRootNode 通过 current
来指向 hostRootFiber。
即创建如下的结构:
由于是首屏渲染,页面中还没有挂载任何DOM
,所以fiberRootNode.current
指向的rootFiber
没有任何子Fiber节点
(即 current Fiber Tree 为空)。
流程二 (render)
接下来进入render阶段
,根据组件返回的JSX在内存中以深度优先原则依次创建wip FiberNode
并连接在一起构建Fiber树,即workInProgress Fiber Tree
。
生成的 wip FiberTree 里面的每一个 FiberNode 会和 current FiberTree 里面的 FiberNode通过alternate
进行关联。但是目前 currentFiberTree里面只有一个 HostRootFiber,因此就只有这个 HostRootFiber 进行了 alternate 的关联。
流程三 (commit)
当 wip FiberTree生成完毕后,进入commit阶段,此时 FiberRootNode就会被传递给 Renderer(渲染器),接下来就是进行渲染工作。已构建完的workInProgress Fiber Tree
在此阶段渲染到页面。
渲染工作完毕后,浏览器中就显示了对应的 UI,此时 FiberRootNode.current 就会指向这颗 wip Fiber Tree,曾经的 wip Fiber Tree 它就会变成 current FiberTree,完成了双缓存的工作:
更新(update)
点击p节点
触发状态改变而更新,这样就进入了update。
流程四 (render)
update会开启一次新的render阶段
并构建一棵新的workInProgress Fiber Tree
,流程和前面一样。
新的 wip Fiber Tree 里面的每一个 FiberNode 和 current Fiber Tree 的每一个 FiberNode 通过 alternate
属性进行关联。
流程五 (commit)
- 当 wip Fiber Tree 生成完毕后,
workInProgress Fiber Tree
就完成了render阶段
的构建,进入commit阶段
渲染到页面上。
FiberRootNode 会被传递给 Renderer 进行渲染,此时宿主环境所渲染出来的真实 UI 对应的就是左边 Fiber Tree (此时还是wip Fiber Tree) 对应的 DOM 结构,FiberRootNode.current 就会指向左边这棵树,右边的树就再次成为了新的 wip Fiber Tree。
以上两棵fiber树交替并更新DOM的过程这就是fiber双缓冲的原理。
扩展
在构建 workInProgress Fiber Tree 时会尝试复用 current Fiber Tree 中对应的 FiberNode 的数据,这个决定是否复用的过程就是 Diff 算法。留个位置放以后讲Diff算法的🔗~
转载自:https://juejin.cn/post/7403185402348306468