《React技术揭秘》学习笔记(一):理念篇(上)

1,242 阅读6分钟

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 浏览器的工作原理

当我们在浏览器输入网址并回车后,会发生如下的事情:

  1. DNS域名解析 -> 2. 建立TCP连接 -> 3. 发送HTTP请求 -> 4. 服务器处理请求 -> 5. 返回响应结果 -> 6. 关闭TCP连接 -> 7. 浏览器解析HTML -> 8. 浏览器布局渲染

而在这里,我们需要详细了解的是浏览器在接收到响应结果之后,对HTML的解析和浏览器布局渲染的过程。浏览器的主要组件结构如下图所示:

图片.png

在浏览器的渲染过程中,主要用到的是渲染引擎(Rendering engine)、JS解释器(JavaScript interpreter)、UI后端(UI backend),它们分别负责解析和渲染资源、解析执行JS代码,以及页面绘制。接下来,我们来看一看渲染流程:

图片.png

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为例子,说明两个版本架构的区别。

  1. React15的架构可以分为两层:
    • Reconciler(协调器) —— 负责找出变化的组件
    • Renderer(渲染器) —— 负责将变化的组件渲染到页面上
  2. React16的架构可以分为三层:
    • Scheduler(调度器) —— 调度任务的优先级,高优任务优先进入Reconcile
    • Reconciler(协调器) —— 负责找出变化的组件
    • Renderer(渲染器) —— 负责将变化的组件渲染到页面上 从它们架构的分层,我们可以看到React16相较于之前的版本,添加了一个Scheduler调度器。那么添加了这个模块有什么好处呢?

在之前的版本中,组件的挂载和更新都是递归更新的。而递归一旦开始执行,中途就无法终端,当层级很深时,递归更新时间超过了16ms,用户交互就会出现卡顿。

而React16中新增的schduler模块,可以以浏览器是否有剩余时间作为任务中断的标准,打断原本进行递归执行的任务。要想打断递归执行的任务,我们就需要把这些庞大的任务分成一个个子任务。而在新的React架构中,Reconciler内部采用Fiber结构,使得原本需要递归执行的操作可以被划分成小的任务片段。