Skip to content

React 的目标与举措 #82

@inkjuncom

Description

@inkjuncom

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 会做如下工作:

  1. 调用函数组件、或 class 组件等 render 方法, 蒋返回的 JSX 转换为虚拟 DOM
  2. 将虚拟 DOM 和上次更新的虚拟 DOM 做对比
  3. 通过对比照出本次更新中变化的虚拟 DOM
  4. 通知 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 的执行顺序为:

  1. 点击 button, state.count 从 1 变为 2
  2. Reconciler 发现 1 需要变为 2,通知 Renderer
  3. Renderer 更新 DOM,1 变为 2
  4. Reconciler 发现 2 需要变为 4,通知 Renderer
  5. Renderer 更新 DOM,2 变为 4
  6. Reconciler 发现 3 需要变为 6,通知 Renderer
  7. Renderer 更新 DOM,3 变为 6

而如果是 React16, 执行顺序为:

第一阶段:Render 阶段 (由 Reconciler 负责)

只负责计算和打标记,绝对不会操作 DOM

  1. Reconciler 开始遍历 Fiber 树;
  2. Reconciler 对比发现第 1 个数字需要从 1 变为 2 -> 打上“需要更新”的标记 (Effect Tag);
  3. Reconciler 继续遍历,发现第 2 个数字需要从 2 变为 4 -> 打上“需要更新”的标记;
  4. Reconciler 继续遍历,发现第 3 个数字需要从 3 变为 6 -> 打上“需要更新”的标记;
  5. Reconciler 收集完所有需要变更的节点,生成一个 Effect List (副作用列表);

(注意:在这个阶段结束时,页面上的数字仍然是 1, 2, 3,DOM 还没有被触碰)

第二阶段:Commit 阶段 (由 Renderer 负责)
拿到变更列表,一口气同步更新 DOM

  1. Renderer 接收到 Effect List;
  2. Renderer 读取列表第一项,更新 DOM (1 -> 2);
  3. Renderer 读取列表第二项,更新 DOM (2 -> 4);
  4. Renderer 读取列表第三项,更新 DOM (3 -> 6);
  5. Renderer 完成所有更新,浏览器进行绘制.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions