Fiber 架构的工作原理

558 阅读5分钟

之前的文章,总结了关于新/老 Fiber 的架构方案,以及能够解决什么问题。

接下来一起来看看他的工作原理,以及如何看源码打debugger。

Fiber架构的工作原理

Fiber的含义

含义一:

我们知道React 老的 Fiber 架构是递归实现的,并且十分依赖 Stack (栈),从而被叫做 Stack Reconciler。

而新的 Fiber 架构依赖的是 Fiber,所以被叫做 Fiber Reconciler。

而Reconciler就是React中的协调器。

含义二:作为静态的数据结构

作为静态的数据结构,每个 Fiber 节点都对应着一个组件,节点中保存该组件的类型、对应的DOM节点的信息。这时候的Fiber节点就是我们常说的虚拟DOM了。

举个例子,我们有一个App组件:

import React, { useState } from "react";
import ReactDOM from "react-dom";

const rootElement = document.getElementById("root")

export default function App() {
    const [num, setNum] = useState(0);
    return (
        <p onClick={() => setNum(num + 1)}>
            sun
            {num}
        </p>
    );
}

ReactDOM.render(
    <App />,
    rootElement
);

上面的代码中,当我们调用ReactDom.render方法时,会创建整个应用的根节点 FiberRootNode

我们是可以多次调用ReactDom.render的,能够将不同的应用挂在到不同的DOM节点中去,所以每个应用都会有自己的根Fiber节点:RootFiber,一个页面中可能会有多个 RootFiber,他们会被唯一的FiberRootNode所管理。

函数组件App会创建一个对应的Fiber节点,在这个Fiber节点的类型是 functionComponent。

然后 App 的子节点p会创建一个Fiber节点,这个Fiber节点的类型为 HooksComponent,意思是原生DOM节点对应的Fiber节点。

p节点的子节点 sun 会创建一个文本Fiber节点,sun的兄弟节点也会创建一个文本Fiber节点。

image.png

思考:

我们知道大概的属性结构,那这些节点是如何进行连接的?

  • FiberRootNode.current 指向 RootFiber
  • RootFiber.child 指向 App
  • App.child 指向 p
  • p.child 指向 sun 文本节点
  • sun.subling 指向 num 文本节点
  • num.return 指向 p

兄弟节点是通过.subling属性进行连接的,一直到最后一个兄弟节点,最后的这个兄弟节点会通过.return告诉父元素。

image.png

能够发现子节点和父节点关系是return,不是parent等其他的。

这是因为老的React架构是通过递归的方式进行的,而新的思想也利用了这样的想法,通过这样的想法进行可中断的递归操作。

含义三:作为动态的工作单元

Fiber 节点保存了主键需要更新的状态,以及需要执行的副作用。

这部分在之后打debugger时详细介绍。

我们来看看下面这些参数:

image.png

红色框框里面是作为静态数据结构时:

  • tag:Fiber对应的主键类型。比如:FunctionComponent、ClassComponent、HooksComponent,其中HooksComponent指DOM节点对应的Fiber节点。
  • key:就是我们常用的key属性。
  • elementType:大部分时候与type相同。
  • type:对于FunctionComponent时是函数本身,对于ClassComponent时是class,对于HooksComponent时是DOM节点的tagName。
  • stateNode:对于HooksComponent来说指的是真实的DOM节点。
  • retrun、child、sibling就不用说了,靠他们组成Fiber树。
  • index:对于多个同级DOM节点,代表他们插入DOM的位置索引。

双缓存

Fiber使用的是一种双缓存的工作方式。接下来介绍双缓存。

什么是双缓存?

我们知道动画是通过一帧一帧进行展示,变成连续的动画。那如果我们在渲染新的一帧之前,要将前一帧的图片清除掉。在清除前一帧图片前,到展示下一帧图片的时候,所要消耗时间过长,这个时候就会出现白屏卡顿。

为了解决这个问题,我们可以在内存中绘制当前帧的图片,在绘制完成之后直接用当前帧替换掉上一帧的图片。这样就省去了替换之间所需要消耗的时间,就不会出现白屏的情况。

这种在内存中构建,并直接替换的技术,就叫做双缓存

Demo 解释 React 如何利用双缓存

export default function App() {
    const [num, setNum] = useState(0);
    return (
        <p onClick={() => setNum(num + 1)}>
          {num}
        </p>
    );
}

ReactDOM.render(
    <App />,
    ...
);

我们知道,当首次调用 ReactDOM.render 的时候会创建 FiberRootNode

之后每次调用ReactDOM.render,都会创建当前应用的根节点 RootFiber。其中FiberRootNode.current指向当前的RootFiber。

由于在首屏渲染之前页面是空白的,所以RootFiber是没有字节点的。

image.png

接下来进入首屏渲染的逻辑:

不管是首屏渲染,还是调用了this.setState,或者调用了useState方法创建了更新。都会从根节点创建一颗Fiber树:

  • 首先创建Fiber树的根节点RootFiber。在两棵Fbier树中都存在的Fiber节点会用alternate属性连接。这会方便两个Fiber之间共用一些属性。

image.png

  • 接下来会采用深度优先遍历,模拟递归的方式创建整颗Fiber树。

image.png

  • 现在我们拥有了两颗Fiber树。左边的是代表页面内容的Fiber树被称作 current Fiber树,右边由于触发了更新在内存中构建的Fiber树被称为 workInProgress Fiber树
  • 当workInProgress Fiber树完成了渲染,此时current指针指向workInProgress Fiber树的根节点,于是workInProgress Fiber树就变成了current Fiber树。
  • 创建完成之后,alternate属性会让两棵树彼此共用。

image.png

  • 接下来我们点击 p 标签,触发一次更新。每次触发更新都会重新创建一个workInProgress Fiber树。
  • 根据alternate属性,进行重新创建workInProgress Fiber树。除了RootFiber,在本次更新中App、p都有对应的current Fiber存在,通过alternate连接共用。

image.png

  • 这种将current Fiber与本次更新返回的JSX结构作对比,从而生成workInProgress Fiber树的过程就是Diff算法。

所以首屏渲染与更新的区别就是是否使用了Diff算法。

  • 当workInProgress Fiber树渲染完成之后, current指针发生改变,指向workInProgress Fiber树的根节点。这时候workInProgress Fiber树就变成了current Fiber树。 image.png

总结

  • Fiber的含义
    • 静态的数据结构
    • 动态的工作单元
    • 作为架构
  • Fiber的数据结构
  • Fiber创缓存的工作原理