-
Notifications
You must be signed in to change notification settings - Fork 0
Description
React 的目标与举措
React 的目标是快速响应, 制约 React 快速响应的因素有:
- CPU 的瓶颈: 大计算量的操作或者设备性能不足使页面掉帧,导致卡顿
Tip
主流浏览器刷新频率是 60Hz (一秒钟刷新 60 次, 约等于 16.67ms 刷新一次), 在 16.67ms 内, 如果不能完成 JS脚本执行 --> 样式布局 --> 样式绘制, 就会造成卡顿
- IO 的瓶颈: 发送网络请求后,由于需要等待数据返回才能进一步操作导致不能快速响应
针对 CPU 的瓶颈, React 把每一帧的 JS 执行时间限定为 5ms, 其余时间留给浏览器绘制 UI, 如果 JS 任务比较长, 5ms 钟之内无法完成, React 会把这个任务中断, 等下一帧时才继续执行
针对 IO 的瓶颈: 由于 React 无法干预网络请求, 它给出的解决方案是, 把同步的 UI 更新改成可中断的异步更新.
以切换城市查看地图为例:
- 同步: 误点北京后界面卡死, 必须等加载完才能改.
- 异步: 误点北京能随时打断, 立刻切换上海, 丝滑无等待.
React15 的架构与缺点
React15 的架构可以分为两部分:
- Reconciler: 协调器, 负责找出变化的组件
- Renderer: 渲染器, 负责蒋变化的组件渲染到页面上
关于 Reconciler, React 中可以通过 this.setState, this.forceUpdate, ReactDOM.render 等 API 出发更新, 每当有更新发生时, Reconciler 会做如下工作:
- 调用函数组件、或 class 组件等 render 方法, 蒋返回的 JSX 转换为虚拟 DOM
- 将虚拟 DOM 和上次更新的虚拟 DOM 做对比
- 通过对比照出本次更新中变化的虚拟 DOM
- 通知 Renderer 将变化的虚拟 DOM 渲染到页面上
关于 Renderer, 由于 React 支持跨平台, 所以不同平台有不同的 Render, 前端最熟悉的是 ReactDOM, 除此之后还有
- ReactNative, 渲染 App 原生组件
- ReactTest, 渲染出纯 JS 对象用于测试
- ReactArt, 渲染到 Canvas、SVG, 目前已废弃, 推荐 React-Knova
React15 架构的缺点
在 React15 的 Reconciler 中, mount 的组件会调用 mountComponent, update 的组件会调用 updateComponent, 这两个方法都会递归更新子组件
由于递归更新, 所以一旦开始,中途就无法中断. 当层级很深时, 递归更新时间超过了 16.67ms, 用户交互就会卡顿
Tip
React15 和 React16 在执行顺序和底层机制上有本质区别
React15 使用的是 Stack Reconciler (栈协调器), 而 React 16 使用的是 Fiber Reconciler (Fiber 协调器)
以下列代码为例子:
import React from "react";
export default class App extends React.Component {
constructor(...props) {
super(...props);
this.state = {
count: 1
};
}
onClick() {
this.setState({
count: this.state.count + 1
});
}
render() {
return (
<ul>
<button onClick={() => this.onClick()}>乘以{this.state.count}</button>
<li>{1 * this.state.count}</li>
<li>{2 * this.state.count}</li>
<li>{3 * this.state.count}</li>
</ul>
);
}
}点击按钮, React15 的执行顺序为:
- 点击 button, state.count 从 1 变为 2
- Reconciler 发现 1 需要变为 2,通知 Renderer
- Renderer 更新 DOM,1 变为 2
- Reconciler 发现 2 需要变为 4,通知 Renderer
- Renderer 更新 DOM,2 变为 4
- Reconciler 发现 3 需要变为 6,通知 Renderer
- Renderer 更新 DOM,3 变为 6
而如果是 React16, 执行顺序为:
第一阶段:Render 阶段 (由 Reconciler 负责)
只负责计算和打标记,绝对不会操作 DOM
- Reconciler 开始遍历 Fiber 树;
- Reconciler 对比发现第 1 个数字需要从 1 变为 2 -> 打上“需要更新”的标记 (Effect Tag);
- Reconciler 继续遍历,发现第 2 个数字需要从 2 变为 4 -> 打上“需要更新”的标记;
- Reconciler 继续遍历,发现第 3 个数字需要从 3 变为 6 -> 打上“需要更新”的标记;
- Reconciler 收集完所有需要变更的节点,生成一个 Effect List (副作用列表);
(注意:在这个阶段结束时,页面上的数字仍然是 1, 2, 3,DOM 还没有被触碰)
第二阶段:Commit 阶段 (由 Renderer 负责)
拿到变更列表,一口气同步更新 DOM
- Renderer 接收到 Effect List;
- Renderer 读取列表第一项,更新 DOM (1 -> 2);
- Renderer 读取列表第二项,更新 DOM (2 -> 4);
- Renderer 读取列表第三项,更新 DOM (3 -> 6);
- Renderer 完成所有更新,浏览器进行绘制.