react源码总结
Posted michael_yqs
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了react源码总结相关的知识,希望对你有一定的参考价值。
React 16 版本中初始渲染的流程
核心过程
JSX -> babel -> createElement -> ReactElement -> render -> workInProgress Fiber -> commit -> getSnapShotBeforeUpdate -> dom -> lifeCircle/hook
逻辑细节
jsx 转换成 react 元素
-
babel-react 会将jsx 调用 React.createElement
-
React.createElement 会 jsx 转换成 react element (react element 就是 一个用来描述react 元素的对象。)
render
(协调层)此阶段负责创建 Fiber 数据结构并为 Fiber 节点打标记,标记当前 Fiber 节点要进行的 DOM 操作。
-
为每一个react 元素构建 fiber 对象 (workInProgress Fiber 树)
- 创建 此 fiber 对象对应的 DOM 对象
- 为 fiber 对象添加 effectTag 属性(用来记录当前 Fiber 要执行的 DOM 操作)
-
render 结束后, fiber 会被保存到 fiberroot 中
具体的代码层步骤:
-
将子树渲染到容器中 (初始化 Fiber 数据结构: 创建 fiberRoot 及 rootFiber)
-
判断是否为服务器端渲染 如果不是服务器端渲染,清空 container 容器中的节点
-
通过实例化 ReactDOMBlockingRoot 类创建 LegacyRoot,创建 LegacyRoot 的 Fiber 数据结构
-
创建 container,创建根节点对应的 fiber 对象
-
获取 container 的第一个子元素的实例对象
-
计算任务的过期时间,再根据任务过期时间创建 Update 任务,将任务(Update)存放于任务队列(updateQueue)中。判断任务是否为同步 调用同步任务入口。
-
构建 workInProgress Fiber 树
commit阶段(渲染层)
-
先获取到render 的结果, 在 fiberroot 中的 新构建的 workInProgress Fiber 树
-
根据 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 生命周期函数。具体流程:
-
处理DOM节点渲染/删除后的 autoFocus、blur逻辑;
-
调用getSnapshotBeforeUpdate生命周期钩子;
-
调度useEffect。
mutation阶段
执行DOM操作,将 workInProgress Fiber 树变成 current Fiber 树:
-
如果该fiber类型是ClassComponent的话,执行getSnapshotBeforeUpdate生命周期api,将返回的值赋到fiber对象的__reactInternalSnapshotBeforeUpdate上;
-
如果该fiber类型是FunctionComponent的话,执行hooks上的effect相关 API。
代码层面:
-
根据ContentReset effectTag重置文字节点;
-
更新ref;
-
根据effectTag分别处理,其中effectTag包括(Placement | Update | Deletion | Hydrating);
-
Placement时:获取父级DOM节点。其中finishedWork为传入的Fiber节点获取Fiber节点的DOM兄弟节点根据DOM兄弟节点是否存在决定调用parentNode.insertBefore或parentNode.appendChild执行DOM插入操作;
-
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)
代码层面:
-
调用componentDidxxx;
-
调用this.setState第二个参数回调函数;
-
调用useLayoutEffect hook的回调函数(与mutation的销毁函数是同步的),调度useEffect的销毁与回调函数(在before mutation只是先调度加入异步任务,在这里才真正执行),因此useLayoutEffect是同步的,useEffect是异步的;
-
获取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源码总结的主要内容,如果未能解决你的问题,请参考以下文章