静态数据结构
每个 Fiber 节点对应一个组件,保存了该组件的类型,对应的 DOM 节点的对应信息。
FiberRootNode 有且仅有一个,而 rootFiber 可以有多个,因为我们可以挂载多个应用(也就是多次调用ReactDOM.render
)
function App(props) {
const [age, setAge] = useState(24);
return (
<div>
chenjiang
{age}
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
最终形成数据结构如下:
Fiber 的数据结构
function FiberNode(tag: WorkTag, pendingProps: mixed, key: null | string, mode: TypeOfMode) {
// Instance
// 组件的类型 FunctionComponent、classComponent、HostComponent(指的是DOM节点对应的Fiber节点)
this.tag = tag;
this.key = key;
// 大多数情况下于tag相同,使用React.memo包裹时候,elementType和tag不同
this.elementType = null;
// 对于FunctionComponent,指函数本身
// 对于ClassComponent,指class
// 对于HostComponet,指的是DOM节点tagName
this.type = null;
// 对于HostComponent来说指的是对应的真实的DOM节点
this.stateNode = null;
// 以下属性用于连接其他Fiber节点形成Fiber树。
// 指向父fiber节点
this.return = null;
// 指向第一个子fiber节点
this.child = null;
// 指向第一个兄弟fiber节点
this.sibling = null;
this.index = 0;
// ref属性
this.ref = null;
// 新传入的 props
this.pendingProps = pendingProps;
// 之前的 props
this.memoizedProps = null;
// 更新队列,用于暂存 setState 的值
this.updateQueue = null;
// 之前的 state
this.memoizedState = null;
this.dependencies = null;
this.mode = mode;
// 保存本次更新会造成的DOM操作。比如删除,移动
this.flags = NoFlags;
this.nextEffect = null;
this.firstEffect = null;
this.lastEffect = null;
this.lanes = NoLanes;
this.childLanes = NoLanes;
//用于链接新树和旧树;旧->新,新->旧
this.alternate = null;
}
动态的工作单元
Fiber 可以理解成一个工作单元,每次执行完一个工作单元,react 就会检查还剩余多少时间,如果没有就把控制权交还给浏览器。React Fiber 与浏览器的核心交互过程如下:
首先 React 向浏览器请求调度,浏览器在一帧中如果还有空闲时间,会去判断是否存在待执行任务,不存在就直接将控制权交给浏览器,如果存在就会执行对应的任务,执行完成后会判断是否还有时间,有时间且有待执行任务则会继续执行下一个任务,否则就会将控制权交给浏览器。
Fiber 可以被理解为划分一个个更小的工作单元,它是把一个大任务拆分为了很多个小块任务,一个小块任务的执行必须是一次完成的,不能出现暂停,但是一个小块任务执行完后可以移交控制权给浏览器去响应用户,从而不用等待大任务一直执行完成再去响应用户。
架构(双缓存工作机制)
什么是双缓存
当我们用canvas
绘制动画,每一帧绘制前都会调用ctx.clearRect
清除上一帧的画面。如果当前帧画面计算量比较大,导致清除上一帧画面到绘制当前帧画面之间有较长间隙,就会出现白屏闪烁。
为了解决这个问题,我们可以在内存中
绘制当前帧动画,绘制完毕后直接用当前帧替换上一帧画面,由于省去了两帧替换间的计算时间,不会出现从白屏到出现画面的闪烁情况。
这种在内存中构建并直接替换的技术叫做双缓存。
React
使用“双缓存”来完成Fiber树
的构建与替换——对应着DOM树
的创建与更新。
双缓存 Fiber 树
React 中存在两棵树,一颗为current Fiber tree
,另外一颗为workInProgress Fiber tree
。
current Fiber tree
也就是当前页面中显示内容对应的 Fiber 树,每个 Fiber 节点成为 current fiber。workInProgress Fiber tree
正在内存中构建的 Fiber 树。
两颗树的 fiber 节点是通过alternate
属性进行连接的。
currentFiber.alternate === workInProgressFiber;
workInProgressFiber.alternate === currentFiber;
React 的根节点(FiberRootNode)通过 current 指针在不同的 Fiber 树的 rootFiber 间切换来实现 Fiber 树的切换。
Fiber 树的构建(挂载阶段、更新阶段)
再次强调:FiberRootNode 有且仅有一个,而 rootFiber 可以有多个,因为我们可以挂载多个应用(也就是多次调用ReactDOM.render
)
以下代码为例:
function App(props) {
const [age, setAge] = useState(24);
return (
<div
onClick={() => {
setAge(25);
}}
>
{age}
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
挂载阶段(mount 阶段)
当首次调用ReactDOM.render
就会创建FiberRootNode
,每次调用ReactDOM.render
就会创建当前应用的根节点RootFiber
,由于在首屏渲染之前页面是空白的,因此RootFiber
不存在子节点。
挂载阶段流程如下(首屏渲染)
首先创建 fiber 树的根节点RootFiber
,在两棵树之间都存在的 Fiber 节点用alternate
连接,接下来采用深度遍历的方式创建 Fiber 树,当workInProgress Fiber tree
完成了渲染,最后 FiberRootNode 的 current 指针就指向workInProgress Fiber tree
的根节点,此时就变成current Fiber tree
。
更新阶段 (update 阶段)
每次触发更新都会重新创建一颗workInProgress Fiber Tree
,current Fiber
与本次更新返回的JSX
做对比,生成workInProgress Fiber
的过程就是 diff 算法,update 阶段与 mount 阶段,最大的区别就是是否有 diff 算法。
参考文章:
- https://juejin.cn/post/6844904202104209415
- https://juejin.cn/post/6943896410987659277