React Fiber架构
Scheduler-Reconciler-Renderer

Fiber 架构

代数效应

代数效应用于将副作用从函数中分离,保持函数的纯粹性

代数效应在React中的应用

For example--->Hooks;对于useState,useReducer,useRef,我们不需要关注FunctionComponent的state在Hook中如何保存

function App() {
    const [count, setCount] = useState(0)
    const handleClick = () => {
        setCount(count => count + 1)
    }
    return (
        <button onClick={handleClick}>{count}</button>
    )
}

代数效应与Generator

从React15到React16Reconciler的架构从同步更新变更到了可中断的异步更新
异步可中断更新:更新的执行过程中随时可能会被中断(浏览器时间分片用尽或者更高优先级的任务插队),当可以继续执行的时候恢复之前执行的中间状态
Generator实现异步可中断更新

代数效应与Fiber

React Fiber: 类似于协程

  • React内部的一套状态更新机制。支持不同的优先级,可以更新和恢复,并且在恢复之后可以服用之前的中间状态
  • 其中每个任务更新单元为React Element对应的Fiber节点---虚拟DOM

Fiber的实现原理

Fiber之前的架构导致的问题: Reconciler采用递归的方式去更新虚拟DOM,递归过程是不能中断的。如果组件树的层级很深,递归就会占用线程大量的时间和资源,造成页面卡顿
React16将递归的无法中断的更新重构为异步的可中断更新,由此产生了新的Fiber架构

What is Fiber

  • 作为架构来说,React15的Reconciler采用递归的方式去执行,数据保存在递归调用栈中---Stack Reconciler。React16的Reconciler基于Fiber节点----Fiber Reconciler
  • 作为静态数据结构来说,每一个Fiber节点对应一个React Element,保存了该组件的类型(class\FunctionComponent\HostComponent)、对应的DOM节点信息
  • 作为动态的工作单元来说,每一个Fiber节点保存了本次更新中该组件改变的状态、要执行的工作等(增加\删除\更新...)

Fiber的结构

Fiber节点定义

function FiberNode (
    tag: WorkTag,
    pendingProps: mixed,
    key: null | string,
    mode: TypeOfMode
) {
    // 作为静态数据结构的属性
    this.tag = tag;
    this.key = key;
    this.elementType = null;
    this.type = null;
    this.statNode = null;

    // 用于链接其他Fiber节点形成Fiber树
    this.return = null;
    this.child = null;
    this.sibling = null;
    this.index = 0;

    this.ref = null;

    // 作为动态的动作单元的属性
    this.penfingProps = pendingProps;
    this.memoizedProps = null;
    this.updateQuene = null;
    this.memoizedState = null;
    this.dependencies = null;

    this.mode = mode;

    this.effectTag = NoEffect;
    this.nextEffect = null;

    this.firstEffect = null;
    this.lastEffect = null;

    //调度优先级
    this.lanes = NoLanes;
    this.childLanes = NoLanes;

    //指向该fiber在另一次更新时候对应的fiber,双缓存的时候链接current Fiber和workInProgress Fiber
    this.alternate = null;
<!-- more -->

<!-- more -->

}

Fiber作为架构

每一个Fiber节点都有对应的React Element,多个Fiber节点应该如何链接成树?

// 指向父级Fiber节点; 指节点执行完completeWork之后会返回的下一个节点
this.return = null;
// 指向子Fiber节点
this.child = null;
// 指向右边第一个兄弟节点
this.sibling = null;

如下示例:

function App() {
    return (
        <div>
            hello
            <span>React</span>
        </div>
    )
}

对应的Fiber树结构:

return指节点执行完completeWork之后会返回的下一个节点,子节点及其兄弟节点在完成工作之后会返回其父级节点

作为静态的数据结构

Fiber作为静态的数据结构,保存了组件的相关信息:

// Fiber对应的组件类型,FunctionComponent/Class/HostComponent...
this.tag = tag;
// key属性
this.key = key;
// 当FunctionComponent使用React.memo包裹的时候type不一样
this.elemenType = null;
// 对于FunctionComponent,指函数本身
// 对于ClassComponent,指class,
// 对于HostComponent,指DOM节点tagName
this.type = null;
// Fiber反应的真实DOM节点
this.stateNode = null; 

作为动态的工作单元

作为动态的工作单元,Fiber保存了本次更新相关的信息

// 保存本次更新造成的状态改变相关信息
this.pendingProps = pendingProps;
this.memoizedProps = null;
this.updateQueue = null;
this.memoizedState = null;
this.dependencies = null;

this.mode = mode;

// 保存本次更新会造成的DOM操作
this.effectTag = NoEffect;
this.nextEffect = null;

this.firstEffect = null;
this.lastEffect = null;

Fiber的工作原理

  • Fiber节点构成Fiber树,Fiber树对应DOM树
  • 直接在内存中构建并直接替换的技术--->双缓存
  • 双缓存技术----Fiber树的构建与替换-->对应着DOM树的构建与更新

双缓存Fiber树

在React中最多存在两棵Fiber树。

  • 当前屏幕上显示内容对应的Fiber树称为Current Fiber树
  • 正在内存中构建的Fiber树,称为workInProgress Fiber树

current Fiber树中的Fiber节点称为current Fiber;同理workInProgress Fiber树中的Fiber节点称为workInProgress Fiber,他们之间通过上文讲过的alternate链接

currentFiber.alternate === workInProgressFiber;
workInProgress.alternate === currentFiber;
  • React应用的根节点通过current指针在不同的Fiber树的rootFiber之间切换来实现Fiber树的切换。
  • 当workInProgress Fiber树构建完成交付给Renderer渲染页面上之后,应用的根节点的current指针指向workInProgress Fiber树,此时workInProgress Fiber树变更为current Fiber 树
  • 每一次的状态更新都会产生新的workInProgress Fiber树,通过current与workInProgress的交换,完成DOM更新。以下为mount和update时的构建与替换流程

mount

example:

function App() {
    const [count, setCount] = useState(0)
    return (
        <p onClick={() => setCount(count + 1)}>{count}</p>
    )
}
ReactDOM.render(<App />, document.getElementById('root'))
    • 首次执行ReactDOM.render会创建fiberRootNode(源码里面的fiberRoot)和rootFiber;其中fiberNodeRoot是整个应用根节点,rootFiber是<App />所在组件树的根节点
    • 区分fiberRootNode和rootFiber的原因在与,在应用中可以多次调用ReactDOM.render渲染不同的组件树,他们拥有不同的rootFiber。但是整个应用的根节点只有一个,就是fiberRootNode
    • fiberRootNode的current会指向当前页面上已经渲染内容对应的Fiber树----current Fiber 树
fiberRootNode.current = rootFiber

由于是首屏渲染,此时页面还没有挂载任何DOM,所以fiberRootNode.current指向的rootFiber没有任何子Fiber节点(current Fiber树为空)

    • 进入render阶段,根据组件返回的JSX在内存中依次创建Fiber 节点并链接在一起构建Fiber树,这个时候被称为workInProgress Fiber树
    • 在构建workInProgress Fiber树时会尝试复用current Fiber树中已经存在的Fiber 节点内的属性,在首屏渲染的时候只有rootFiber存在对应的current Fiber(rootFiber.alternate)

Fiber render阶段:

    • 上图所示的已经构建完成的workInProgress Fiber树在commit阶段渲染到页面
    • 此时页面中DOM被重新渲染,current指针指向workInProgress Fiber树,使其成为Current Fiber树

update

    • 我们点击p标签的时候会触发onclick。改变状态值,这个操作会开启一次新的render阶段并构建一个新的workInProgress Fiber树
      图示如下:
    • 与mount的时候一样,workInProgress Fiber的创建可以复用current fiber树对应的节点数据
      这个决定是否使用的过程就是diff算法
    • workInProgress Fiber 树在render阶段完成构建之后进入commit阶段渲染到页面上。在渲染完毕之后,当前的workInProgress Fiber树变更为current Fiber树

Fiber树的构建和替换的过程会伴随着DOM的更新

React的Scheduler-Reconciler-Renderer架构体系

  • Reconciler工作的阶段被称作render阶段。因为在该阶段调用组件的render方法
  • Renderer阶段被称为commit阶段《类似于git commit》,commit阶段会把render提交的信息渲染在页面上
  • render与commit阶段的统称为work,也就是React的工作流程,如果任务处于Scheduler内部进行调度,不是work

JSX与Fiber节点

  • JSX是一种描述当前组件数据类型的数据结构,不包含schedule、reconcile、render所需要的相关信息
    • 组件在更新中的优先级
    • 组件的state
    • 组件被打上用于Reconciler的标记
  • 在组件mount的时候,Reconciler根据 JSX描述的组件内容生成对应的Fiber节点
  • 在组件update的时候,Reconciler将JSX与Fiber节点保存的数据进行对比,生成组件对应的Fiber节点,并根据对应的结果给Fiber节点打上标记