React框架解析

116 阅读6分钟

1. React的架构

React16 架构可以分为三层:

  • Scheduler(调度器)—— 调度任务的优先级,高优任务优先进入Reconciler
  • Reconciler(协调器)—— 负责找出变化的组件
  • Renderer(渲染器)—— 负责将变化的组件渲染到页面上

Scheduler(调度器)

以浏览器是否有剩余时间作为任务中断的标准,当浏览器有剩余时间时通知我们。

由于以下因素,React放弃使用浏览器API requestIdleCallback:

  • 浏览器兼容性
  • 触发频率不稳定,受很多因素影响。比如当我们的浏览器切换 tab 后,之前 tab 注册的requestIdleCallback触发的频率会变得很低

Reconciler(协调器)

  • 更新工作从递归变成了可以中断的循环过程。每次循环都会调用shouldYield判断当前是否有剩余时间。
  • Scheduler将任务交给Reconciler后,Reconciler会为变化的虚拟 DOM 打上代表增/删/更新的标记
  • 整个SchedulerReconciler的工作都在内存中进行。只有当所有组件都完成Reconciler的工作,才会统一交给Renderer

Renderer(渲染器)

Renderer根据Reconciler为虚拟 DOM 打的标记,同步执行对应的 DOM 操作。

2. Fiber架构

Fiber本质上是虚拟DOMFiber节点可以保存对应的DOM节点;相应的,Fiber节点构成的Fiber树就对应DOM树

Fiber的含义

  1. 从架构来说,React16Reconciler基于Fiber节点实现,被称为Fiber Reconciler
  2. 从数据结构来说,每个Fiber节点对应一个React element,保存了该组件的类型(函数组件/类组件/原生组件)、对应的DOM节点等信息。
  3. 从工作单元来说,每个Fiber节点保存了本次更新中该组件改变的状态、要执行的工作(需要被删除/被插入页面中/被更新)。

注释:

Fiber数据结构里,type属性,值为字符串即原生标签、值为函数且type.prototype.isReactComponent即类组件、值为函数且!type.prototype.isReactComponent即函数组件、值为undefined即文本。

多个Fiber节点是如何连接形成树?

// 指向父级Fiber节点
this.return = null;
// 指向子Fiber节点
this.child = null;
// 指向右边第一个兄弟Fiber节点
this.sibling = null;

3. Fiber架构的工作原理

什么是“双缓存?

在内存中构建并直接替换的技术叫做[双缓存]

双缓存 Fiber 树

React中最多会同时存在两棵Fiber树。当前屏幕上显示内容对应的Fiber树称为current Fiber树,正在内存中构建的Fiber树称为workInProgress Fiber树

初次渲染的构建流程

  1. 首次执行ReactDOM.render会创建fiberRootNoderootFiber。其中fiberRootNode是整个应用的根节点,rootFiber<App/>所在组件树的根节点。
    • 由于是首屏渲染,页面中还没有挂载任何DOM,所以fiberRootNode.current指向的rootFiber没有任何子Fiber节点(即current Fiber树为空)。
  2. render阶段: 接下来进入render阶段,根据组件返回的JSX在内存中依次创建 Fiber 节点并连接在一起构建Fiber树,被称为workInProgress Fiber树
    • 在构建workInProgress Fiber树时会尝试复用current Fiber树中已有的 Fiber 节点内的属性,在首屏渲染时,只有rootFiber存在对应的current fiber(即rootFiber.alternate)。
  3. commit阶段: 已构建完的workInProgress Fiber树在commit阶段渲染到页面。

更新阶段的构建流程

  1. render阶段: 会开启一次新的render阶段并构建一棵新的workInProgress Fiber 树
    • workInProgress fiber的创建可以复用current Fiber树对应的节点数据。
  2. commit阶段: workInProgress Fiber 树render阶段完成构建后进入commit阶段渲染到页面上。渲染完毕后,workInProgress Fiber 树变为current Fiber 树

JSX 与 Fiber 节点

区别:

JSX是一种描述当前组件内容的数据结构,他不包含组件schedulereconcilerender所需的相关信息。

不包括在JSX中的信息:

  • 组件在更新中的优先级
  • 组件的state
  • 组件被打上的用于Renderer标记

初次渲染和更新阶段:

在组件mount时,Reconciler根据JSX描述的组件内容生成组件对应的Fiber节点

update时,ReconcilerJSXFiber节点保存的数据对比,生成组件对应的Fiber节点,并根据对比结果为Fiber节点打上标记

4. render阶段

render阶段的工作可以分为“递”阶段和“归”阶段。其中“递”阶段会执行beginWork,“归”阶段会执行completeWork

“递”阶段

beginWork的工作可以分为两部分:

  • mount时:除fiberRootNode以外,current === null。会根据fiber.tag不同,创建不同类型的子Fiber节点
  • update时:如果current存在,在满足一定条件时可以复用current节点,这样就能克隆current.child作为workInProgress.child,而不需要新建workInProgress.child

“归”阶段

由于completeWork属于“归”阶段调用的函数,每次调用appendAllChildren时都会将已生成的子孙DOM节点插入当前生成的DOM节点下。那么当“归”到rootFiber时,已经有一个构建好的离屏DOM树

  • mount时,主要逻辑包括三个:

    • Fiber节点生成对应的DOM节点
    • 将子孙DOM节点插入刚生成的DOM节点
    • update逻辑中的updateHostComponent类似的处理props的过程
  • update时,Fiber节点已经存在对应DOM节点,所以不需要生成DOM节点。需要做的主要是处理props,比如:

    • onClickonChange等回调函数的注册
    • 处理style prop
    • 处理DANGEROUSLY_SET_INNER_HTML prop
    • 处理children prop

commit阶段

effectList

completeWork的上层函数completeUnitOfWork中,每个执行完completeWork且存在effectTagFiber节点会被保存在一条被称为effectList的单向链表中。

effectList中第一个Fiber节点保存在fiber.firstEffect,最后一个元素保存在fiber.lastEffect

所以,在commit阶段只需要遍历effectList就能执行所有effect了。

commit 阶段的工作流程

commit阶段的主要工作(即Renderer的工作流程)分为三部分:

  • before mutation 阶段(执行DOM操作前)
  • mutation 阶段(执行DOM操作)
  • layout 阶段(执行DOM操作后)

1. before mutation 阶段

before mutation阶段,会遍历effectList,依次执行:

  1. 处理DOM节点渲染/删除后的 autoFocusblur逻辑
  2. 调用getSnapshotBeforeUpdate生命周期钩子
  3. 调度useEffect

2. mutation 阶段

mutation阶段会遍历effectList,“根据effectTag调用不同的处理函数处理Fiber

3. layout 阶段

该阶段触发的生命周期钩子和hook可以直接访问到已经改变后的DOM,即该阶段是可以参与DOM layout的阶段。

layout阶段会遍历effectList,“根据effectTag调用不同的处理函数处理Fiber并更新ref