react源码总结

Posted michael_yqs

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了react源码总结相关的知识,希望对你有一定的参考价值。

核心过程

JSX -> babel -> createElement -> ReactElement -> render -> workInProgress Fiber -> commit -> getSnapShotBeforeUpdate -> dom -> lifeCircle/hook

逻辑细节

jsx 转换成 react 元素

  1. babel-react 会将jsx 调用 React.createElement

  2. React.createElement 会 jsx 转换成 react element (react element 就是 一个用来描述react 元素的对象。)

render

(协调层)此阶段负责创建 Fiber 数据结构并为 Fiber 节点打标记,标记当前 Fiber 节点要进行的 DOM 操作。

  1. 为每一个react 元素构建 fiber 对象 (workInProgress Fiber 树)

    1. 创建 此 fiber 对象对应的 DOM 对象
    2. 为 fiber 对象添加 effectTag 属性(用来记录当前 Fiber 要执行的 DOM 操作)
  2. render 结束后, fiber 会被保存到 fiberroot 中

具体的代码层步骤

  • 将子树渲染到容器中 (初始化 Fiber 数据结构: 创建 fiberRoot 及 rootFiber)

  • 判断是否为服务器端渲染 如果不是服务器端渲染,清空 container 容器中的节点

  • 通过实例化 ReactDOMBlockingRoot 类创建 LegacyRoot,创建 LegacyRoot 的 Fiber 数据结构

  • 创建 container,创建根节点对应的 fiber 对象

  • 获取 container 的第一个子元素的实例对象

  • 计算任务的过期时间,再根据任务过期时间创建 Update 任务,将任务(Update)存放于任务队列(updateQueue)中。判断任务是否为同步 调用同步任务入口。

  • 构建 workInProgress Fiber 树

commit阶段(渲染层)

  1. 先获取到render 的结果, 在 fiberroot 中的 新构建的 workInProgress Fiber 树

  2. 根据 fiber 中的 effectTag 属性进行相应的 DOM 操作

为什么 React 16 版本中 render 阶段放弃了使用递归

  • 因为主流浏览器的刷新频率为60Hz,即每(1000ms / 60Hz)16.6ms浏览器刷新一次。JS可以操作DOM,GUI渲染线程与JS线程是互斥的。所以JS脚本执行和浏览器布局、绘制不能同时执行。超过16.6ms就会让用户感知到卡顿。

  • 16以前的版本采用递归执行。递归耗内存,它使用 javascript 自身的执行栈,更新一旦开始,中途就无法中断。当VirtualDOM 树的层级很深时,virtualDOM 的比对就会长期占用 JavaScript 主线程,递归更新的时间就会超过16ms,由于 JavaScript 又是单线程的无法同时执行其他任务,所以在比对的过程中无法响应用户操作,无法即时执行元素动画,造成了页面卡顿的现象。

  • 而React16架构可以分为三层:Scheduler,Reconciler,Renderer,与之前不同的是Reconciler和Renderer不再交替执行,而是当Scheduler将任务交给Reconciler后,Reconciler会为变化的虚拟DOM打上代表增/删/更新的标记,整个Scheduler与Reconciler的工作都在内存中进行。只有当所有组件都完成Reconciler的工作,才会统一交给Renderer。并且采用双缓存用作统一替换,用户也不会看到更新不完全的真实dom。它放弃了 JavaScript 递归的方式进行 virtualDOM 的比对,而是采用循环模拟递归。而且比对的过程是利用浏览器的空闲时间完成的,不会长期占用主线程,这就解决了 virtualDOM 比对造成页面卡顿的问题。

fiber架构的特点(react16后使用 )

  • 可拆分,可中断任务

  • 可重用各分阶段任务,且可以设置优先级

  • 可以在父子组件任务间前进后退切换任务

  • render方法可以返回多元素(即可以返回数组)

  • 支持异常边界处理异常

采用循环模拟递归。而且比对的过程是利用浏览器的空闲时间完成的,不会长期占用主线程,这就解决了 virtualDOM 比对造成页面卡顿的问题。

react16版本中commit阶段的三个子阶段

before mutation阶段

执行DOM操作前,处理类组件的 getSnapShotBeforeUpdate 生命周期函数。具体流程:

  1. 处理DOM节点渲染/删除后的 autoFocus、blur逻辑;

  2. 调用getSnapshotBeforeUpdate生命周期钩子;

  3. 调度useEffect。

mutation阶段

执行DOM操作,将 workInProgress Fiber 树变成 current Fiber 树:

  • 如果该fiber类型是ClassComponent的话,执行getSnapshotBeforeUpdate生命周期api,将返回的值赋到fiber对象的__reactInternalSnapshotBeforeUpdate上;

  • 如果该fiber类型是FunctionComponent的话,执行hooks上的effect相关 API。

代码层面:

  1. 根据ContentReset effectTag重置文字节点;

  2. 更新ref;

  3. 根据effectTag分别处理,其中effectTag包括(Placement | Update | Deletion | Hydrating);

  4. Placement时:获取父级DOM节点。其中finishedWork为传入的Fiber节点获取Fiber节点的DOM兄弟节点根据DOM兄弟节点是否存在决定调用parentNode.insertBefore或parentNode.appendChild执行DOM插入操作;

  5. Update时:执行所有useLayoutEffect hook的销毁函数。调用commitWork;6.Deletion时:递归调用Fiber节点及其子孙Fiber节点中fiber.tag为ClassComponent的componentWillUnmount (opens new window)生命周期钩子,从页面移除Fiber节点对应DOM节点解绑ref调度useEffect的销毁函数。

layout

执行 DOM 操作后,commitHookEffectList()阶段,调用类组件生命周期函数或者函数组件的钩子函数:

  • 重置 nextEffect,useEffect是让FunctionComponent产生副作用的hooks,当使用useEffect后,会在fiber上的updateQueue.lastEffect生成effect链,具体请看ReactFiberHooks.js中的pushEffect()

  • 作用:循环FunctionComponent上的effect链,并根据每个effect上的effectTag,执行destroy/create操作(作用类似于componentDidMount/componentWillUnmount)

代码层面:

  1. 调用componentDidxxx;

  2. 调用this.setState第二个参数回调函数;

  3. 调用useLayoutEffect hook的回调函数(与mutation的销毁函数是同步的),调度useEffect的销毁与回调函数(在before mutation只是先调度加入异步任务,在这里才真正执行),因此useLayoutEffect是同步的,useEffect是异步的;

  4. 获取DOM实例,更新ref5.current Fiber树切换(workInProgress Fiber树在commit阶段完成渲染后会变为current Fiber树)。

workInProgress Fiber 的意义

实现双缓存技术, 在内存中构建 DOM 结构以及 DOM 更新, 在 commit 阶段实现 DOM 的快速更新.

  • 当我们用canvas绘制动画,每一帧绘制前都会调用ctx.clearRect清除上一帧的画面。如果当前帧画面计算量比较大,导致清除上一帧画面到绘制当前帧画面之间有较长间隙,就会出现白屏。为了解决这个问题,我们可以在内存中绘制当前帧动画,绘制完毕后直接用当前帧替换上一帧画面,由于省去了两帧替换间的计算时间,不会出现从白屏到出现画面的闪烁情况。这种在内存中构建并直接替换的技术叫做双缓存 。

  • 在React中最多会同时存在两棵Fiber树。当前屏幕上显示内容对应的Fiber树称为current Fiber树,正在内存中构建的Fiber树称为workInProgress Fiber树,它反映了要刷新到屏幕的未来状态。current Fiber树中的Fiber节点被称为current fiber,workInProgress Fiber树中的Fiber节点被称为workInProgress fiber,他们通过alternate属性连接。React应用的根节点通过current指针在不同Fiber树的rootFiber间切换来实现Fiber树的切换。当workInProgress Fiber树构建完成交给Renderer渲染在页面上后,应用根节点的current指针指向workInProgress Fiber树,此时workInProgress Fiber树就变为current Fiber树。每次状态更新都会产生新的workInProgress Fiber树,通过current与workInProgress的替换,完成DOM更新。由于有两颗fiber树,实现了异步中断时,更新状态的保存,中断回来以后可以拿到之前的状态。并且两者状态可以复用,节约了从头构建的时间。

  • workInProgress在内存中构建,构建完成才统一替换,这样不会产生不完全的真实dom。

以上是关于react源码总结的主要内容,如果未能解决你的问题,请参考以下文章

react源码总结

react源码总结

React Fiber源码分析 (介绍)

React高频面试题梳理,看看面试怎么答?(上)

读react源码准备

react源码解析19.手写迷你版react