2024-08-27 10:10:05 +08:00

196 lines
8.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Fiber双缓冲
> 面试题:谈一谈你对 React 中 Fiber 的理解以及什么是 Fiber 双缓冲?
## 对 Fiber 的理解
实际上,我们可以从三个维度来理解 Fiber
- 是一种架构,称之为 Fiber 架构
- 是一种数据类型
- 动态的工作单元
**是一种架构,称之为 Fiber 架构**
在 React v16之前使用的是 Stack Reconciler因此那个时候的 React 架构被称之为 Stack 架构。从 React v16 开始,重构了整个架构,引入了 Fiber因此新的架构也被称之为 Fiber 架构Stack Reconciler 也变成了 Fiber Reconciler。各个FiberNode之间通过链表的形式串联起来
```js
function FiberNode(tag, pendingProps, key, mode) {
// ...
// 周围的 Fiber Node 通过链表的形式进行关联
this.return = null;
this.child = null;
this.sibling = null;
this.index = 0;
// ...
}
```
**是一种数据类型**
Fiber 本质上也是一个对象,是在之前 React 元素基础上的一种升级版本。每个 FiberNode 对象里面会包含 React 元素的类型、周围链接的FiberNode以及 DOM 相关信息:
```js
function FiberNode(tag, pendingProps, key, mode) {
// 类型
this.tag = tag;
this.key = key;
this.elementType = null;
this.type = null;
this.stateNode = null; // 映射真实 DOM
// ...
}
```
**动态的工作单元**
在每个 FiberNode 中,保存了本次更新中该 React 元素变化的数据,还有就是要执行的工作(增、删、更新)以及副作用的信息:
```js
function FiberNode(tag, pendingProps, key, mode) {
// ...
// 副作用相关
this.flags = NoFlags;
this.subtreeFlags = NoFlags;
this.deletions = null;
// 与调度优先级有关
this.lanes = NoLanes;
this.childLanes = NoLanes;
// ...
}
```
> 为什么指向父 FiberNode 的字段叫做 return 而非 parent
>
> 因为作为一个动态的工作单元return 指代的是 FiberNode 执行完 completeWork 后返回的下一个 FiberNode这里会有一个返回的动作因此通过 return 来指代父 FiberNode
## Fiber 双缓冲
Fiber 架构中的双缓冲工作原理类似于显卡的工作原理。
显卡分为前缓冲区和后缓冲区。首先,前缓冲区会显示图像,之后,合成的新的图像会被写入到后缓冲区,一旦后缓冲区写入图像完毕,就会前后缓冲区进行一个互换,这种将数据保存在缓冲区再进行互换的技术,就被称之为双缓冲技术。
Fiber 架构同样用到了这个技术,在 Fiber 架构中,同时存在两颗 Fiber Tree一颗是真实 UI 对应的 Fiber Tree可以类比为显卡的前缓冲区另外一颗是在内存中构建的 FiberTree可以类比为显卡的后缓冲区。
在 React 源码中,很多方法都需要接收两颗 FiberTree
```js
function cloneChildFibers(current, workInProgress){
// ...
}
```
current 指的就是前缓冲区的 FiberNodeworkInProgress 指的就是后缓冲区的 FiberNode。
两个 FiberNode 会通过 alternate 属性相互指向:
```js
current.alternate = workInProgress;
workInProgress.alternate = current;
```
接下来我们从首次渲染mount和更新update这两个阶段来看一下 FiberTree 的形成以及双缓存机制:
**mount 阶段**
首先最顶层有一个 FiberNode称之为 FiberRootNode该 FiberNode 会有一些自己的任务:
- Current Fiber Tree 与 Wip Fiber Tree 之间的切换
- 应用中的过期时间
- 应用的任务调度信息
现在假设有这么一个结构:
```html
<body>
<div id="root"></div>
</body>
```
```jsx
function App(){
const [num, add] = useState(0);
return (
<p onClick={() => add(num + 1)}>{num}</p>
);
}
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<App />);
```
当执行 ReactDOM.createRoot 的时候,会创建如下的结构:
<img src="https://xiejie-typora.oss-cn-chengdu.aliyuncs.com/2023-02-24-071516.png" alt="image-20230224151515483" style="zoom:50%;" />
此时会有一个 HostRootFiberFiberRootNode 通过 current 来指向 HostRootFiber。
接下来进入到 mount 流程,该流程会基于每个 React 元素以深度优先的原则依次生成 wip FiberNode并且每一个 wipFiberNode 会连接起来,如下图所示:
<img src="https://xiejie-typora.oss-cn-chengdu.aliyuncs.com/2023-02-24-072421.png" alt="image-20230224152421236" style="zoom:50%;" />
生成的 wip FiberTree 里面的每一个 FiberNode 会和 current FiberTree 里面的 FiberNode进行关联关联的方式就是通过 alternate。但是目前 currentFiberTree里面只有一个 HostRootFiber因此就只有这个 HostRootFiber 进行了 alternate 的关联。
当 wip FiberTree生成完毕后也就意味着 render 阶段完毕了,此时 FiberRootNode就会被传递给 Renderer渲染器接下来就是进行渲染工作。渲染工作完毕后浏览器中就显示了对应的 UI此时 FiberRootNode.current 就会指向这颗 wip Fiber Tree曾经的 wip Fiber Tree 它就会变成 current FiberTree完成了双缓存的工作
<img src="https://xiejie-typora.oss-cn-chengdu.aliyuncs.com/2023-02-24-072953.png" alt="image-20230224152953358" style="zoom:50%;" />
**update 阶段**
点击 p 元素,会触发更新,这一操作就会开启 update 流程,此时就会生成一颗新的 wip Fiber Tree流程和之前是一样的
<img src="https://xiejie-typora.oss-cn-chengdu.aliyuncs.com/2023-02-24-073250.png" alt="image-20230224153250126" style="zoom:48%;" />
新的 wip Fiber Tree 里面的每一个 FiberNode 和 current Fiber Tree 的每一个 FiberNode 通过 alternate 属性进行关联。
当 wip Fiber Tree 生成完毕后就会经历和之前一样的流程FiberRootNode 会被传递给 Renderer 进行渲染,此时宿主环境所渲染出来的真实 UI 对应的就是左边 wip Fiber Tree 所对应的 DOM 结构FiberRootNode.current 就会指向左边这棵树,右边的树就再次成为了新的 wip Fiber Tree
<img src="https://xiejie-typora.oss-cn-chengdu.aliyuncs.com/2023-02-24-073639.png" alt="image-20230224153638862" style="zoom:50%;" />
这个就是 Fiber双缓存的工作原理。
另外值得一提的是,开发者是可以在一个页面创建多个应用的,比如:
```js
ReactDOM.createRoot(rootElement1).render(<App1 />);
ReactDOM.createRoot(rootElement2).render(<App2 />); ReactDOM.createRoot(rootElement3).render(<App3 />);
```
在上面的代码中,我们创建了 3 个应用,此时就会存在 3 个 FiberRootNode以及对应最多 6 棵 Fiber Tree 树。
## 真题解析
> 题目:谈一谈你对 React 中 Fiber 的理解以及什么是 Fiber 双缓冲?
>
> 参考答案:
>
> Fiber 可以从三个方面去理解:
>
> - **FiberNode 作为一种架构**:在 React v15 以及之前的版本中Reconceiler 采用的是递归的方式,因此被称之为 Stack Reconciler到了 React v16 版本之后,引入了 FiberReconceiler 也从 Stack Reconciler 变为了 Fiber Reconceiler各个 FiberNode 之间通过链表的形式串联了起来。
> - **FiberNode 作为一种数据类型**Fiber 本质上也是一个对象,是之前虚拟 DOM 对象React 元素createElement 的返回值)的一种升级版本,每个 Fiber 对象里面会包含 React 元素的类型,周围链接的 FiberNodeDOM 相关信息。
> - **FiberNode 作为动态的工作单元**:在每个 FiberNode 中,保存了“本次更新中该 React 元素变化的数据、要执行的工作增、删、改、更新Ref、副作用等”等信息。
>
> 所谓 Fiber 双缓冲树,指的是在内存中构建两颗树,并直接在内存中进行替换的技术。在 React 中使用 Wip Fiber Tree 和 Current Fiber Tree 这两颗树来实现更新的逻辑。Wip Fiber Tree 在内存中完成更新,而 Current Fiber Tree 是最终要渲染的树,两颗树通过 alternate 指针相互指向,这样在下一次渲染的时候,直接复用 Wip Fiber Tree 作为下一次的渲染树,而上一次的渲染树又作为新的 Wip Fiber Tree这样可以加快 DOM 节点的替换与更新。