一、 请简述 React 16 版本中初始渲染的流程
jsx转换为react元素
babel-react将jsx调用React.createElement创建为虚拟DOM(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树
一些关键点:
- 在render阶段中,只会做一些复杂的运算,并不会真正的操作页面
- 在内存中做 新旧 fiber对象比对,找出更新的fiber节点,或者是首次加载时 生成组装html片段
render阶段是可以被打断的,但初始渲染时不会被打断,因为要让用户尽快看到界面react里的时间分片的概念,分的就是复杂运算的部分render阶段也就是说可能会被高优先级的任务(例如界面事件)打断
commit阶段 (渲染层):commit阶段负责根据Fiber节点标记 (effectTag) 进行相应的DOM操作,可以分为三个子阶段
before mutation阶段(执行DOM操作前):处理类组件的getSnapShotBeforeUpdate生命周期函数mutation阶段(执行DOM操作): 根据effectTag执行DOM操作,向容器container中追加或插入节点layout阶段(执行DOM操作后):调用生命周期函数和钩子函数,执行render传递的回调函数
二、 为什么 React 16 版本中 render 阶段放弃了使用递归
- 在
React 15的版本中,采用了循环加递归的方式进行了virtualDOM的比对,由于递归使用JavaScript自身的执行栈,一旦开始就无法停止,直到任务执行完成。如果VirtualDOM树的层级比较深,virtualDOM的比对就会长期占用JavaScript主线程,由于JavaScript又是单线程的无法同时执行其他任务,所以在比对的过程中无法响应用户操作,无法即时执行元素动画,造成了页面卡顿的现象。 - 在
React 16的版本中,放弃了JavaScript递归的方式进行virtualDOM的比对,而是采用循环模拟递归。而且比对的过程是利用浏览器的空闲时间完成的,不会长期占用主线程,这就解决了virtualDOM比对造成页面卡顿的问题。 - 在
React中,官方实现了自己的任务调度库Scheduler。它可以实现在浏览器空闲时执行任务,而且还可以设置任务的优先级,高优先级任务先执行,低优先级任务后执行。
三、 请简述 React 16 版本中 commit 阶段的三个子阶段分别做了什么事情
调用 commitRoot 表示进入 commit 阶段,commitRoot 更改任务优先级,默认任务优先级为 97 ,commit阶段不可被打断,要以最高优先级执行 commit 任务,使用 runWithPriority 改变任务优先级并调用 commitRootImpl 则开始 commit 阶段
before mutation阶段(执行DOM操作前) 主要调用类组件生命周期函数getSnapshotBeforeUpdate,并且把旧的props和旧的states传递进去mutation阶段(执行DOM操作) 调用commitMutationEffects,获取对象得effects,根据不同的effectTag执行不同的操作: 插入节点:commitPlacement、更新节点:commitWork、删除节点:commitDeletionlayout阶段(执行DOM操作后) 调用类组件的生命周期函数: 初次渲染阶段调用componentDidMount生命周期函数、更新阶段调用componentDidUpdate生命周期函数、执行渲染完成之后的回调函数,也就是render函数的第三个参数,并且更改this指向,指向render方法的第一个参数 调用函数组件的钩子函数: firstEffect:指向第一个更新的节点、nextEffect:指向下一个更新的节点
四、 请简述 workInProgress Fiber 树存在的意义是什么
React使用双缓存技术完成Fiber树的构建与替换,实现DOM对象的快速更新- 在
React中最多会同时存在两棵Fiber树,当前在屏幕中显示的内容对应的Fiber树叫做current Fiber树,当发生更新时,React会在内存中重新构建一颗新的Fiber树,这颗正在构建的Fiber树叫做workInProgress Fiber树。在双缓存技术中,workInProgress Fiber树就是即将要显示在页面中的Fiber树,当这颗Fiber树构建完成后,React会使用它直接替换current Fiber树达到快速更新DOM的目的,因为workInProgress Fiber树是在内存中构建的所以构建它的速度是非常快的。一旦workInProgress Fiber树在屏幕上呈现,它就会变成current Fiber树 - 在
current Fiber节点对象中有一个alternate属性指向对应的workInProgress Fiber节点对象,在workInProgress Fiber节点中有一个alternate属性也指向对应的current Fiber节点对象。