了解 与 理解
React
是用 JavaScript
构建快速响应的大型 Web
应用程序的首选方式。它在 Facebook
和 Instagram
上表现优秀。
我们日常使用
App
,以及浏览网页的时候,有两个种类会影响到快速响应
:- 当遇到大计算量的操作 或者 设备性能不足的时候 会使页面掉帧,导致卡顿。
- 当发送网络请求后,由于需要等待数据返回才能进一步操作,导致页面不能快速响应。
结合以上的两种情况可以概括为:
- CPU瓶颈
- IO瓶颈
CPU 瓶颈
当项目变大、组件数量多,就会遇到CPU的瓶颈。
主流浏览器刷新频率为60Hz,即每(1000ms / 60Hz)16.6ms浏览器刷新一次。
我们知道,JS
可以操作 DOM
,GUI渲染线程
与 JS线程
是互斥的。所以 JS执行
和 layout
、 paint
不能同时执行。
在每 16.6ms
时间内,需要完成如下工作:
JS执行 ----- 样式布局 ----- 样式绘制
当 JS
执行时间过长,超出了16.6ms,这次刷新就没有时间执行 样式布局
和 绘制
了。
React
如何解决这个问题呢?- 在浏览器每一帧的时间中,预留一些时间给
JS
线程,React
利用这部分时间更新组件(可以看到,在 源码 中,预留的初始时间是5ms)。
- 在浏览器每一帧的时间中,预留一些时间给
当预留的时间不够用时,React
将线程控制权交还给浏览器使其有时间渲染 UI
,React
则等待下一帧时间到来继续被中断的工作。
这种将长任务分拆到每一帧中,变成每次执行一小段任务的操作,被称为时间切片(time slice)。
将👆理解为切片上传
所以,解决CPU瓶颈的关键是实现时间切片,而时间切片的关键是:将同步的更新变为可中断的异步更新。
IO的瓶颈
网络延迟
是前端开发者无法解决的。如何在 网络延迟
客观存在的情况下,减少用户对 网络延迟
的感知?
React
给出了一份答案 查看。
当 “一小段时间” 足够短时,用户是无感知的。如果请求时间超过一个范围,再显示 loading
的效果。
为此,React实现了 Suspense
功能及配套的hook—— useDeferredValue
。
而在源码内部,目标也是解决CPU的瓶颈与IO的瓶颈。所以为了支持这些特性同样需要将 同步的更新
变为 可中断的异步更新
。
版本更新
从使用到现在,历届版本更改的内容。
0.13 版本
使用
- 在
React 0.13.0
中你不再需要使用它React.createClass
来创建React
组件。如果您有babel-polyfill
转译器,可以直接使用 ES6 类。 - 添加了新的顶级 API
React.findDOMNode(component)
,应使用它来代替component.getDOMNode()
。 基于 ES6 的组件的基类不会有getDOMNode
。 - 添加了一个新的顶级 API
React.cloneElement(el, props)
。 - 支持迭代器和
immutable-js
序列作为子项。 React.addons.createFragment
添加用于向整组子项添加键。- class 默认情况下方法不再是可枚举的,这需要
Object.defineProperty
来设置是否可枚举; 如果您支持IE8
等浏览器,则可以通过--target es3镜像旧行为
- 在
弃用
React.addons.classSet
现已弃用。此功能可以替换为多个免费可用的模块。 官方推荐classnames
。- 废弃了一些辅助性的
API
。
React
在15
版本以前都是小版本,都是0.xx
的版本。React
在0.13
版本的时候可以使用es6
的class
, 之前实现的class 类
都是React
自己内部实现的。- 克隆节点是在
0.13
版本出现的 - 在
0.13
版本的时候就可以使用immutable-js
就可以使用不可改变对象了。 0.13
之前版本的类是可枚举的。- React.createElement('DIV') div 不是小写的偶尔出现问题。
select
节点渲染option
渲染不是百分之百正确的。
0.14 版本
使用
- 第一次了解到
Webpack
是React
的0.14
的版本推荐。 - 把
React
和ReacDom
拆分成两个包 这个举动直接为编写RN
和web
提供了便利。 this.refs
获取的是实际的DOM
节点。- 支持服务端渲染
react-dom/server
- 编译器第一次优化 转换将
optimisation.react.constantElements
完全静态的子树的元素创建提升到顶层,从而减少了调用React.createElement
和由此产生的分配。更重要的是,它告诉React
子树没有改变,因此React
在协调时可以完全跳过它。 - React DOM 支持了一大堆的标准
HTML
属性。
- 第一次了解到
废弃
- ReactDOM.getDOMNode() 并将其替换为 ReactDOM.findDOMNode。
0.14
真正把React
推向了SPA
应用,也是React
推出SSR
的第一个版本。this.refs
之前获取的是虚拟DOM
对象。- 增强了对
CANVAS
的支持。
v15 版本
- 使用
- 增强了
SVG
的支持。
- 增强了
React 15 之前的视图更新
- 开启批量更新
- 走合成事件过程 调用
dispatchEvent
,batchedUpdates
,最终会调用到我们自己绑定事件的方法上。 - 将要更改的组件状态通过
updater.enqueueSetState
放入当前组件的状态的pendingStateQueue
数组中。 - 将当前组件实例放入待更新队列中
- 循环组件队列,获取每个待更新组件 用
isBatchUpdate
来鉴定当前组件是否需要更新 - 合并组件中放入的所有状态,得到一个最终的状态
- 每个组件递归更新所有子组件(这里会有
dom-diff
操作,然后在更新) 通过flushBatchedUpdates
- 把待更新队列置空
- 通过
dityComponents
方法把批量更新开关关闭
setState
是同步还是异步通过 isBatchingUpdates
来鉴定。 true
就是异步 , false
就是同步。
React
内部提供了强制异步更新的操作,ReactDOM.unstable_batchedUpdates
。
React
的思想就是数据不可变的,只要使用 setState
视图就会更新。如果想优化,PureComponent
。
PureComponent
原理也很简单,就是通过对前后状态进行浅比较实现 shouldComponentUpdate
钩子返回不同的值。
React15
中,由 Reconciler
计算出变化的组件,然后交给 stack Renderer
,Renderer
更新视图到页面上,这里面有两个问题,一是这里组件是树结构,更新的时候是会递归更新,二是一旦开始递归更新,无法打断执行,如果这里卡死的话,那么用户什么操作也执行不了。
v16.18 版本(更改核心架构)
React16
实际上比15
更小109kb
之前161.7kb
React15
对使用unstable_handleError
该方法已重命名为componentDidCatch
setState
回调(第二个参数)现在在componentDidMount
之后立即触发componentDidUpdate
,而不是在所有组件渲染之后触发。hooks
渲染机制是同步更新的
在React15中,有 Reconciler(协调器)
、Renderer(渲染器)
这两个机制负责 Dom
的更新和渲染。
Reconciler:本次更新中哪些节点需要更新,在相应的虚拟 DOM
打上标记,然后交给 Renderer
渲染器,Diff
算法就是发生在这个阶段。这里指虚拟 DOM
节点的更新,并不是视图层的更新
Renderer:负责渲染更新的虚拟 DOM
,根据不同的内容或环境使用不同的渲染器。如渲染 jsx
内容使用 ReactTest
渲染器,虚拟 DOM
使用浏览器V8引擎或 SSR
渲染
对于当前组件需要更新内容是依次更新,Reconciler
发现一个需要更新的节点后就交给 Renderer
渲染器渲染。完成后 Reconciler
又发现下个需要更新的节点,再交给 Renderer
渲染器. 直到此次更新内容全部完成,整个更新流程是同步执行的。
缺陷:如果在更新过程中突然终止,会造成后面需要更新的内容全部中断,即更新失败。原因就是渲染的底层机制是同步进行的。
React16
的渲染机制借用了代数效应的思想,即渲染过程可异步。在异步渲染过程中,不会销毁后面待更新的内容;且异步更新完成后,回来接着上次更新的内容继续更新。
React16
中,新增了 Scheduler(调度器)
,其底层渲染机制由 Scheduler
、Reconciler
、Renderer
三者共同完成。
Scheduler:负责调度更新节点的优先级,给需要更新的节点打上不同的标记,再交给Reconiler,Reconiler会根据不同的优先级更新对应的虚拟DOM。
渲染流程:Scheduler调度器给节点打上标记后,交给Reconciler协调器,Reconciler会执行优先级较高的节点,更新对应的虚拟DOM。当Scheduler有新的优先级比较高的节点交给Reconciler时,Reconciler会暂停正在执行的节点,并不会销毁,此时发生异步更新。当异步更新完成后,接着回来执行上次操作的节点。直到此次组件跟新内容全部打上标签后,再移交到Renderer渲染器渲染,统一更新到视图层。 React16渲染机制中的异步更新就是代数效应的思想,Scheduler和Reconciler发生在计算机内存中,所以交互速度快得飞起,不同担心耗时问题。
React16
多了个 Scheduler
调度器,负责调度不同情况下节点更新的优先级
React15
是同步更新节点,Reconciler
更新一个 DOM
节点,Renderer
更新一次视图,递归更新;React16
是 Reconciler
将虚拟 DOM
节点全部更新完成后才移交给 Renderer
更新。
React16
不使用 Generator
的原因
虽然 Generator
也能实现可中断的异步更新,但不能满足优先级可控的要求,所以没有使用 Generator
实现 Scheduler(调度器)