0. 前言
在小编寻找 react 源码分析的相关资料时,淘到宝了 —— 卡颂大神的《React技术揭秘》。这本书并没有从日常开发的 API入手,而是从理念到架构,从架构到实现,从实现到具体代码,自顶向下、抽象程度递减,符合认知的过程。因此,对于读者还是比较友好的。
这本书分为三个部分:
- 理念篇
- 架构篇
- 实现篇
这篇文章,我们来跟随这本书的角度,聊一聊 React 的设计理念。这篇文章会分为以下五个部分:
- React 设计理念:这部分回答 react 解决什么问题
- 新老框架对比:以React16为分界,这部分来聊一聊这两个框架有什么区别和相似的地方
- Fiber相关:Fiber是React16之后版本的核心单元
- 源码相关:这部分主要内容是React的文件结构
- 深入理解JSX:这部分内容是关于JSX的
1. React设计理念
首先,我们从React官网中可以看到React
的理念:
我们认为,React是用JavaScript构建快速响应的大型 Web 应用程序的首选方式。
这段话的关键是快速响应。因此,我们对于React理念的讨论变成了React如何实现快速响应。实现快速响应的关键是解决CPU瓶颈和IO瓶颈。解决这两个瓶颈之前,我们需要先了解一下浏览器的工作原理。
1.1 浏览器的工作原理
当我们在浏览器输入网址并回车后,会发生如下的事情:
- DNS域名解析 -> 2. 建立TCP连接 -> 3. 发送HTTP请求 -> 4. 服务器处理请求 -> 5. 返回响应结果 -> 6. 关闭TCP连接 -> 7. 浏览器解析HTML -> 8. 浏览器布局渲染
而在这里,我们需要详细了解的是浏览器在接收到响应结果之后,对HTML的解析和浏览器布局渲染的过程。浏览器的主要组件结构如下图所示:
在浏览器的渲染过程中,主要用到的是渲染引擎(Rendering engine)、JS解释器(JavaScript interpreter)、UI后端(UI backend),它们分别负责解析和渲染资源、解析执行JS代码,以及页面绘制。接下来,我们来看一看渲染流程:
webkit渲染引擎的流程如上图,它包括了HTML文件的解析和CSS文件的解析,并且将两者解析成的DOM树和CSS规则树附着合成形成渲染树,交由UI后端进行页面绘制。这个过程中,需要注意的是DOM树的生成过程可能被CSS和JS的加载执行阻塞。当浏览器遇到script
标记时,DOM树构建将暂停,直至脚本完成执行,然后继续构建DOM树。每次去执行JavaScript脚本都会严重地阻塞DOM树的构建,如果JavaScript脚本好操作了CSSOM,而正好这个CSSOM还没有下载和构建浏览器甚至会延迟脚本执行和构建DOM,直至完成CSSOM的下载和构建。
关于浏览器的内容,我们最后再来看一下浏览器的渲染帧。渲染帧是指浏览器一次完整绘制过程,帧之间的时间间隔是DOM视图更新的最小间隔。由于主流的屏幕刷频率都在60Hz,那么渲染一帧的时间就必须控制在16ms才能保证不掉帧。也就是说每一次渲染都要在16ms内,页面才能够流畅,不会有卡顿感。这段时间内浏览器需要完成如下事情:
- 脚本执行(JavaScript):脚本造成了需要重绘的改动,比如增删DOM,请求动画等
- 样式计算(CSS Object Model):级联地生成每个结点的生效样式
- 布局(Layout):计算布局,执行渲染算法
- 重绘(Paint):各层分别进行绘制
- 合成(Composite):合成各层地渲染结果
浏览器工作原理的参考资料如下:
1.2 CPU瓶颈
我们在实际中遇到的大计算量的操作或者设备性能不足使页面掉帧,导致卡顿。这种情况说明我们遇到了CPU瓶颈。在浏览器的工作原理中我们可以看到,当JS执行的时间过长,会导致页面的卡顿。因此,要解决CPU瓶颈,关键还是要压缩JS的执行时间。那么,在React中是如何解决这个问题的呢?
答案是:在浏览器的每一帧事件中,预留一些时间给JS线程,React利用这部分时间更新组件。当预留的时间不够用时,React将线程的控制权交还给浏览器,使其有时间渲染UI。React则等待下一帧时间到来继续被中断的工作。这种长任务被分拆到每一帧中,像蚂蚁搬家一样依次执行小段任务的操作,被称为时间切片(time slice)
1.3 IO瓶颈
造成IO瓶颈的主要原因是网络延迟。网络延迟不是前端开发者可以解决的问题,我们只能从用户感知的角度来解决IO瓶颈。React的解决方案是将人机交互研究的结果整合到真实的UI中。
2. 新老架构对比
以React16为分界线,React的架构发生了很大变化。这里我们以React15和React16为例子,说明两个版本架构的区别。
- React15的架构可以分为两层:
- Reconciler(协调器) —— 负责找出变化的组件
- Renderer(渲染器) —— 负责将变化的组件渲染到页面上
- React16的架构可以分为三层:
- Scheduler(调度器) —— 调度任务的优先级,高优任务优先进入Reconcile
- Reconciler(协调器) —— 负责找出变化的组件
- Renderer(渲染器) —— 负责将变化的组件渲染到页面上
从它们架构的分层,我们可以看到React16相较于之前的版本,添加了一个
Scheduler
调度器。那么添加了这个模块有什么好处呢?
在之前的版本中,组件的挂载和更新都是递归更新的。而递归一旦开始执行,中途就无法终端,当层级很深时,递归更新时间超过了16ms,用户交互就会出现卡顿。
而React16中新增的schduler
模块,可以以浏览器是否有剩余时间作为任务中断的标准,打断原本进行递归执行的任务。要想打断递归执行的任务,我们就需要把这些庞大的任务分成一个个子任务。而在新的React架构中,Reconciler
内部采用Fiber结构,使得原本需要递归执行的操作可以被划分成小的任务片段。