Skip to main content

了解 与 理解

React 是用 JavaScript 构建快速响应的大型 Web 应用程序的首选方式。它在 FacebookInstagram 上表现优秀。

  • 我们日常使用 App,以及浏览网页的时候,有两个种类会影响到 快速响应

    1. 当遇到大计算量的操作 或者 设备性能不足的时候 会使页面掉帧,导致卡顿。
    2. 当发送网络请求后,由于需要等待数据返回才能进一步操作,导致页面不能快速响应。
  • 结合以上的两种情况可以概括为:

    1. CPU瓶颈
    2. IO瓶颈

CPU 瓶颈

当项目变大、组件数量多,就会遇到CPU的瓶颈。

主流浏览器刷新频率为60Hz,即每(1000ms / 60Hz)16.6ms浏览器刷新一次。

我们知道,JS 可以操作 DOMGUI渲染线程JS线程 是互斥的。所以 JS执行layoutpaint 不能同时执行。

在每 16.6ms 时间内,需要完成如下工作:

JS执行 -----  样式布局 ----- 样式绘制

JS 执行时间过长,超出了16.6ms,这次刷新就没有时间执行 样式布局绘制 了。

  • React 如何解决这个问题呢?
    • 在浏览器每一帧的时间中,预留一些时间给 JS 线程,React 利用这部分时间更新组件(可以看到,在 源码 中,预留的初始时间是5ms)。

当预留的时间不够用时,React 将线程控制权交还给浏览器使其有时间渲染 UIReact 则等待下一帧时间到来继续被中断的工作。

这种将长任务分拆到每一帧中,变成每次执行一小段任务的操作,被称为时间切片(time slice)。

将👆理解为切片上传

所以,解决CPU瓶颈的关键是实现时间切片,而时间切片的关键是:将同步的更新变为可中断的异步更新。

IO的瓶颈

网络延迟 是前端开发者无法解决的。如何在 网络延迟 客观存在的情况下,减少用户对 网络延迟 的感知?

React 给出了一份答案 查看

当 “一小段时间” 足够短时,用户是无感知的。如果请求时间超过一个范围,再显示 loading 的效果。

为此,React实现了 Suspense 功能及配套的hook—— useDeferredValue

而在源码内部,目标也是解决CPU的瓶颈与IO的瓶颈。所以为了支持这些特性同样需要将 同步的更新 变为 可中断的异步更新

版本更新

从使用到现在,历届版本更改的内容。

0.13 版本

  • 使用

    1. React 0.13.0 中你不再需要使用它 React.createClass 来创建 React 组件。如果您有 babel-polyfill 转译器,可以直接使用 ES6 类。
    2. 添加了新的顶级 API React.findDOMNode(component) ,应使用它来代替 component.getDOMNode() 。 基于 ES6 的组件的基类不会有 getDOMNode
    3. 添加了一个新的顶级 API React.cloneElement(el, props)
    4. 支持迭代器和 immutable-js 序列作为子项。
    5. React.addons.createFragment 添加用于向整组子项添加键。
    6. class 默认情况下方法不再是可枚举的,这需要 Object.defineProperty 来设置是否可枚举; 如果您支持 IE8 等浏览器,则可以通过--target es3镜像旧行为
  • 弃用

    1. React.addons.classSet 现已弃用。此功能可以替换为多个免费可用的模块。 官方推荐 classnames
    2. 废弃了一些辅助性的 API
注意
  1. React15 版本以前都是小版本,都是 0.xx 的版本。
  2. React0.13 版本的时候可以使用 es6class , 之前实现的 class 类 都是 React 自己内部实现的。
  3. 克隆节点是在 0.13 版本出现的
  4. 0.13 版本的时候就可以使用 immutable-js 就可以使用不可改变对象了。
  5. 0.13 之前版本的类是可枚举的。
  6. React.createElement('DIV') div 不是小写的偶尔出现问题。
  7. select 节点渲染 option 渲染不是百分之百正确的。

0.14 版本

  • 使用

    1. 第一次了解到 WebpackReact0.14 的版本推荐。
    2. ReactReacDom 拆分成两个包 这个举动直接为编写 RNweb 提供了便利。
    3. this.refs 获取的是实际的 DOM 节点。
    4. 支持服务端渲染 react-dom/server
    5. 编译器第一次优化 转换将 optimisation.react.constantElements 完全静态的子树的元素创建提升到顶层,从而减少了调用 React.createElement 和由此产生的分配。更重要的是,它告诉 React 子树没有改变,因此 React 在协调时可以完全跳过它。
    6. React DOM 支持了一大堆的标准 HTML 属性。
  • 废弃

    1. ReactDOM.getDOMNode() 并将其替换为 ReactDOM.findDOMNode。
注意
  1. 0.14 真正把 React 推向了 SPA 应用,也是 React 推出 SSR 的第一个版本。
  2. this.refs 之前获取的是 虚拟DOM 对象。
  3. 增强了对 CANVAS 的支持。

v15 版本

  • 使用
    1. 增强了 SVG 的支持。
注意

React 15 之前的视图更新

  1. 开启批量更新
  2. 走合成事件过程 调用dispatchEventbatchedUpdates,最终会调用到我们自己绑定事件的方法上。
  3. 将要更改的组件状态通过 updater.enqueueSetState 放入当前组件的状态的 pendingStateQueue 数组中。
  4. 将当前组件实例放入待更新队列中
  5. 循环组件队列,获取每个待更新组件 用 isBatchUpdate 来鉴定当前组件是否需要更新
  6. 合并组件中放入的所有状态,得到一个最终的状态
  7. 每个组件递归更新所有子组件(这里会有 dom-diff 操作,然后在更新) 通过 flushBatchedUpdates
  8. 把待更新队列置空
  9. 通过 dityComponents方法把批量更新开关关闭

setState 是同步还是异步通过 isBatchingUpdates 来鉴定。 true 就是异步 , false 就是同步。 React 内部提供了强制异步更新的操作,ReactDOM.unstable_batchedUpdatesReact 的思想就是数据不可变的,只要使用 setState 视图就会更新。如果想优化,PureComponentPureComponent 原理也很简单,就是通过对前后状态进行浅比较实现 shouldComponentUpdate 钩子返回不同的值。

React15 中,由 Reconciler 计算出变化的组件,然后交给 stack RendererRenderer 更新视图到页面上,这里面有两个问题,一是这里组件是树结构,更新的时候是会递归更新,二是一旦开始递归更新,无法打断执行,如果这里卡死的话,那么用户什么操作也执行不了。

v16.18 版本(更改核心架构)

  1. React16 实际上比 15 更小 109kb 之前 161.7kb
  2. React15 对使用 unstable_handleError 该方法已重命名为 componentDidCatch
  3. setState 回调(第二个参数)现在在c omponentDidMount 之后立即触发 componentDidUpdate ,而不是在所有组件渲染之后触发。
  4. hooks
React15 的底层渲染原理

渲染机制是同步更新的

在React15中,有 Reconciler(协调器)Renderer(渲染器) 这两个机制负责 Dom 的更新和渲染。 Reconciler:本次更新中哪些节点需要更新,在相应的虚拟 DOM 打上标记,然后交给 Renderer 渲染器,Diff 算法就是发生在这个阶段。这里指虚拟 DOM 节点的更新,并不是视图层的更新

Renderer:负责渲染更新的虚拟 DOM ,根据不同的内容或环境使用不同的渲染器。如渲染 jsx 内容使用 ReactTest 渲染器,虚拟 DOM 使用浏览器V8引擎或 SSR 渲染 对于当前组件需要更新内容是依次更新,Reconciler 发现一个需要更新的节点后就交给 Renderer 渲染器渲染。完成后 Reconciler 又发现下个需要更新的节点,再交给 Renderer 渲染器. 直到此次更新内容全部完成,整个更新流程是同步执行的。 缺陷:如果在更新过程中突然终止,会造成后面需要更新的内容全部中断,即更新失败。原因就是渲染的底层机制是同步进行的。

React16 的底层渲染机制

React16 的渲染机制借用了代数效应的思想,即渲染过程可异步。在异步渲染过程中,不会销毁后面待更新的内容;且异步更新完成后,回来接着上次更新的内容继续更新。 React16中,新增了 Scheduler(调度器) ,其底层渲染机制由 SchedulerReconcilerRenderer 三者共同完成。

Scheduler:负责调度更新节点的优先级,给需要更新的节点打上不同的标记,再交给Reconiler,Reconiler会根据不同的优先级更新对应的虚拟DOM。

渲染流程:Scheduler调度器给节点打上标记后,交给Reconciler协调器,Reconciler会执行优先级较高的节点,更新对应的虚拟DOM。当Scheduler有新的优先级比较高的节点交给Reconciler时,Reconciler会暂停正在执行的节点,并不会销毁,此时发生异步更新。当异步更新完成后,接着回来执行上次操作的节点。直到此次组件跟新内容全部打上标签后,再移交到Renderer渲染器渲染,统一更新到视图层。 React16渲染机制中的异步更新就是代数效应的思想,Scheduler和Reconciler发生在计算机内存中,所以交互速度快得飞起,不同担心耗时问题。

React15 和 React16 渲染机制的区别

React16 多了个 Scheduler 调度器,负责调度不同情况下节点更新的优先级 React15 是同步更新节点,Reconciler 更新一个 DOM 节点,Renderer 更新一次视图,递归更新;React16Reconciler 将虚拟 DOM 节点全部更新完成后才移交给 Renderer 更新。 React16 不使用 Generator 的原因 虽然 Generator 也能实现可中断的异步更新,但不能满足优先级可控的要求,所以没有使用 Generator 实现 Scheduler(调度器)