了解 与 理解
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.7kbReact15对使用unstable_handleError该方法已重命名为componentDidCatchsetState回调(第二个参数)现在在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(调度器)