React 理念
# React 理念
# 制约网页快速响应的两个场景
两者的核心都在于:将同步的更新变为可中断的异步更新。
# CPU 瓶颈
原因:
由于 JS 线程和 GUI 线程不能同时执行,当页面复杂,显示内容多时,一次渲染中 JS 执行需要的时间超过浏览器刷新一帧的时间,就可能导致掉帧。
解决办法:
时间切片
即在浏览器每一帧的时间中,预留一些时间给 JS 线程(默认是 5ms (opens new window))。 当预留的时间不够用时,React 将线程控制权交还给浏览器使其有时间渲染 UI,等待下一帧继续未完成的工作
# IO 瓶颈
IO 瓶颈受限于网络延迟,是前端无法解决的。一种优化思路是触发异步 IO 时,间隔“一小段时间”,再跳转。 如果这个间隔足够短,就不显示 loading,以达到让用户无感的目的。 为此,React 实现了 Suspense 组件,用于控制异步组件加载。及配套的 useDeferredValue (opens new window)
# 关于 useDeferredValue
useDeferredValue 用于延迟更新一部分的 UI。
useDeferredValue 接收一个原始值 A,返回这个值的延迟版本 B:
- 首次渲染时,B 和 A 一样
- 当 A 改变触发更新时,React 会进行两次 re-render,第一次时 B 还是更新前的 A,第二次 re-render 时 B 会变为更新后的 B
保留延迟版本的使用场景(也是官网给的两个例子):
- 在新数据加载过程中,显示原来的老数据
export default function App() {
const [query, setQuery] = useState("")
const deferredQuery = useDeferredValue(query)
return (
<>
<label>
Search albums:
<input value={query} onChange={(e) => setQuery(e.target.value)} />
</label>
<Suspense fallback={<h2>Loading...</h2>}>
<SearchResults query={deferredQuery} />
</Suspense>
</>
)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- 对于一部分渲染比较耗时的 UI,将其延迟更新,以保证其他部分流畅渲染。比如根据输入框内容渲染长列表,每次输入时都会整个重新渲染导致输入卡顿。使用 useDeferredValue 可以将列表的渲染延迟,保证输入框的输入流畅
import { useState, useDeferredValue } from "react"
import SlowList from "./SlowList.js"
export default function App() {
const [text, setText] = useState("")
const deferredText = useDeferredValue(text)
return (
<>
<input value={text} onChange={(e) => setText(e.target.value)} />
<SlowList text={deferredText} />
</>
)
}
2
3
4
5
6
7
8
9
10
11
12
13
# React 15 架构的局限
React 15 架构分为两层:
- Reconciler(协调器)—— 负责找出变化的组件
- Renderer(渲染器)—— 负责将变化的组件渲染到页面上
而更新时的过程是二者交替完成,即协调器每找到一处更新,渲染器就进行一次 DOM 更新。Reconciler 中,会递归处理子组件。递归执行一旦开始,则无法中断,当递归更新时间超过了 16ms,就会表现出掉帧。
因此,15 的架构不满足快速响应的理念
# React 16 架构的更新
# Scheduler
React 16 的架构分为三层,在 15 的基础上多了调度器 Scheduler 。
由于更新任务的中断,是以浏览器是否有空余时间为标准的,因此需要一种“浏览器通知我们有剩余时间”的机制。
部分浏览器提供了requestIdleCallback (opens new window) 这个 API,而 Scheduler 也提供了这个功能,此外还支持不同的调度优先级。
# Reconciler 的改变
v16 中,采用 Fiber 架构替代了原有的 stack reconciler 架构。
- Reconciler 不在是递归执行,而是变为可中断的循环。每次循环时会判断当前是否有空余时间
- Reconciler 与 Renderer 不再是交替工作 。Reconciler 会先在虚拟 DOM 上进行标记,当所有组件都完成标记后,Renderer 再一次性进行渲染
由于 Reconciler 的标记过程是内存中运行不影响 DOM,因此是可中断的。
# 基本包结构
# 1. react
基础包,提供定义组件的必要函数,需要和渲染器(针对不同平台)一同使用。 开发中使用的 API 基本都来自这个包。
# 2. react-dom 渲染器
针对 浏览器/nodejs 平台的 react 渲染器,将 react-reconciler 中的运行结果输出到界面上。
开发中能用到的 API 基本只有 ReactDOM.render
# 3. react-reconciler 构造器
- 装载渲染器(必须实现 HostConfig 协议 (opens new window),如 react-dom)
- 接受渲染器的更新请求,将 fiber 树的构造过程包装为回调函数,传入 scheduler
# 4. scheduler 调度器
调度机制的核心实现,其调度机制是可以独立于 react 的。 其核心就是控制回调函数(reconciler 提供)的执行时机。 concurrent 模式下会实现任务分片,使更新变为可中断的异步更新
# 核心运行链路
React 作为视图层框架的主干逻辑,就是将输入转换为输出
- 输入:每次更新(节点的增删改)是一次更新需求
- reconciler 接受到更新需求后,去 scheduler 中注册一个新任务 task
- 一部分是构造更新 fiber
- 把 fiber 渲染到视图
- 输出:scheduler 调度任务,执行 task
为了提高性能,在这条链路上 react 做了很多优化,如可中断渲染、批量更新等