React 相关
返回 相关私有笔记 · 相关私有笔记
📑 本目录笔记
- Hooks — Hooks 链表(memoizedState/next)与 useState/useEffect 源码
- 类组件 vs 函数组件 — 全方位对比
渲染流程
- React Fiber 架构新增了一个
Scheduler 调度器主要用于调度 Fiber 节点的生成和更新任务 - 当组件更新时,
Reconciler 协调器执行组件的 render 方法生成一个Fiber 节点之后再递归的去生成Fiber 节点的子节点 - 每一个
Fiber 节点的生成都是一个单独的任务,会以回调的形式交给Scheduler进行调度处理,在Scheduler里会根据任务的优先级去执行任务 - 任务的优先级
- React 17 引入了
车道模型 Lane作为基础(Concurrent 特性此时仍是实验性的,到 React 18 才正式对外),将任务进行分类,每一类拥有不同的优先级,所有的分类和优先级都在 React 中进行了枚举 - React 16 中使用的是
expirationTime 模型来决定优先级
- React 17 引入了
Scheduler按照优先级执行任务时,会异步的执行,同时每一个任务执行完成之后,都会判断下一个任务是否能在当前渲染帧的剩余时间内完成(早期方案是requestIdleCallback,但因兼容性和触发频率问题被弃用,最终改用MessageChannel实现时间切片)- 如果不能完成就发生中断,把线程的控制权交给浏览器,剩下的任务则在下一个渲染帧内执行
diff(reconcile)发生在 render 阶段:Reconciler和Scheduler在 render 阶段完成 diff 计算后,会生成一个新的workInProgressFiber节点树并打好变更标记;之后Reconciler触发Commit 阶段,通知Render 渲染器把已算好的变更patch到真实 DOM 上
❓ Fiber
有 current Fiber 和 workingProgress Fiber 两棵树,通过
alternate相连
源码解析
React 源码大致可以分为三块:
- 🆕 Scheduler(调度器):排序优先级,让优先级高的任务先进行 reconcile
- Reconciler(协调器):找出哪些节点发生了改变,并打上不同的 Tag
- Renderer(渲染器):将 Reconciler 中打好标签的节点渲染到视图上
整个生命周期的话又大致分为两个阶段:render 和 commit

render 阶段
Reconciler(协调器)是在 render 阶段工作的,简单来说就是会创建、更新 Fiber 节点。通过 Diff 算法给 Fiber 树打上 tag
- 首次渲染时根据 JSX 构建 workingProgress Fiber -> current Fiber
- 每次更新的时候根据变更后的 JSX 形成 workingProgress Fiber
将 workingProgress Fiber 替换为 current Fiber 后渲染到 DOM 中就完成了组件的更新
Fiber 树的一切操作都是在内存中进行的,从而减少对 DOM 的操作,提高性能
遍历 Fiber 树
beginWork主要负责创建 Fiber 节点、计算 state、diff 算法completeWork则处理节点props、创建 DOM、创建effectList链表(被标了更新的节点形成的链表)等
commit 阶段
Renderer(渲染器)是在 commit 阶段工作的,commit 阶段会遍历 render 阶段生成的 effectList,并执行真实 DOM 节点的操作和一些生命周期(不同平台对应的 Renderer 不同,浏览器对应的是 react-dom)
hooks
为函数组件引入状态管理和生命周期方法,提高代码的可复用性和抽象性
React 为每个组件维护了一个 hooks 的单链表,组件每次更新时都会检查链表中的 hook 是否发生变化,并根据变化来更新状态链中的对应状态
mount 阶段(初始化)

来看两个比较重要的函数
export function renderWithHooks(
current,
workInProgress,
Component,
props,
secondArg,
nextRenderExpirationTime,
) {
renderExpirationTime = nextRenderExpirationTime;
currentlyRenderingFiber = workInProgress;
workInProgress.memoizedState = null;
workInProgress.updateQueue = null;
workInProgress.expirationTime = NoWork;
ReactCurrentDispatcher.current =
current === null || current.memoizedState === null
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate;
let children = Component(props, secondArg);
if (workInProgress.expirationTime === renderExpirationTime) {
// 如果当前函数组件执行后,当前函数组件的还是处于渲染优先级
// 说明函数组件又有了新的更新任务,那么循环执行函数组件
}
ReactCurrentDispatcher.current = ContextOnlyDispatcher;
renderExpirationTime = NoWork;
currentlyRenderingFiber = null;
currentHook = null
workInProgressHook = null;
didScheduleRenderPhaseUpdate = false;
return children;
}// ReactFiberHooks.new.js
function mountWorkInProgressHook() {
const hook: Hook = {
memoizedState: null, // useState中 保存 state信息 | useEffect 中 保存着 effect 对象 | useMemo 中 保存的是缓存的值和deps | useRef中保存的是ref 对象
baseState: null,
baseQueue: null,
queue: null,
next: null,
};
if (workInProgressHook === null) { // 例子中的第一个`hooks`-> useState(0) 走的就是这样。
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
} else {
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}Fiber 的 memoizedState 指向的是当前函数形成的 hooks 链表
Hook 对象中的 memoizedState 存储的是 hooks 相关的数据, next 指向的是链表中下一个 hook 节点
因为是链表的原因,从下图就可以很简单的看出来,如果在条件语句中执行的话,可能会导致 hook 执行顺序出错

useState
有两个关键的点:mountState 和 dispatchAction
mountState:首先会得到初始化的state,将它赋值给mountWorkInProgressHook产生的hook对象的memoizedState和baseState属性,然后创建一个queue对象,里面保存了负责更新的信息dispatchAction:无论是类组件还是函数组件,都会生成一个update对象,这个对象上记录了这次更新的信息,并被放入pending队列中。如果当前 Fiber 节点正在渲染阶段,则更新expirationTime;如果不处于更新阶段,那么将新的state和currentState,进行浅比较,值不相等时才调用scheduleUpdateOnFiber更新渲染当前 Fiber
useEffect
mountEffect:初始化 hook 对象并将 hook 相关信息存入 hook 的memoizedState字段中pushEffect:创建一个effect,判断组件如果第一次渲染,那么创建componentUpdateQueue,就是workInProgressFiber 的updateQueue。然后将effect放入updateQueue中

update 阶段
- 从 current Fiber 上取出
memoizedState,拿到旧的 hooks 链表 - 声明并赋值
nextWorkInProgressHook- 正常情况下,应该为
null - 但是如果当前 Fiber 的渲染优先级在下次渲染时还是高的,那么就会赋值为
workInProgressHook.next
- 正常情况下,应该为
- 最后,用
currentHook生成一个新的 Hook 对象,赋值给workInProgressHook,用于更新新一轮 hooks 状态
useState
- 将 hook 产生的
update对象放入pendingQueue。下次函数组件被执行时,将这三个update合并到 hook 对象的baseQueue中 - 把
baseQueue和baseState更新到最新; - 循环
baseQueue,执行每个 action,得到最新的 state;更新expirationTime
总结


Diff 算法
React 的 Diff 算法 可以分为 单节点 Diff 和 多节点 Diff
-
单节点 Diff 相对简单,包含以下流程:
- 首先会判断老的 Fiber 树上有没有对应的 Fiber 节点,若没有则说明是新增操作,直接在老 Fiber 树上新增节点并更新 DOM
- 若老 Fiber 节点也存在,则判断节点上的
key值是否相同,若不同则删除老节点并新增新节点 - 若
key值相同,则判断节点的type是否相同,若不同则删除老节点并新增节点 - 若
type值也相同,则认为是一个可复用的节点,直接返回老节点就行
-
多节点 Diff:主要用于 map 返回多个相同节点的情况下,可以分为三种情况:新增节点、删除节点以及节点移动,React 采用双重遍历的方式来进行三种情况的判断,流程如下:
- 将
children[i]和currentFiber比较,如果可复用,则比较children[i++]和currentFiber.sibling,如果可复用,则继续重复此步骤 - 当上面的第一轮遍历无提前结束时,说明所有节点都可以复用,直接返回老节点
- 若
children遍历完成,currentFiber未完成,则说明是删除操作,需要对未完成的currentFiber兄弟节点标记删除 - 若
children遍历未完成,currentFiber完成,则说明是新增操作,需要生成新的workInProgressFiber节点 - 若
children和currentFiber都未完成,则说明是节点位置发生了变更,那就对剩余的currentFiber进行遍历,并通过key值找到每一个节点在children中对应的老节点,并将老节点中的位置替换为新节点的
- 将
Scheduler
为了实现异步可中断更新,React 使用 MessageChannel 实现了 Scheduler
React 17 之前,Scheduler 中每个任务的优先级是按距离过期时间来排的,离过期时间越近越优先。但是这个模型还存在着一些问题,比如交互操作可能会晚于拉取数据的操作,优先级反转了
Lane
Lane 用二进制位表示优先级,二进制中的 1 表示位置,同一个二进制数可以有多个相同优先级的位,这就可以表示 ” 批次 ” 的概念,而且二进制方便计算
事件机制
合成事件
React 合成事件的优势:
- 抹平不同浏览器直接的差异,提供统一的 API 使用体验
- 通过事件委托的方式统一绑定和分发事件,有利于提升性能,减少内存消耗
合成事件的绑定及分发流程
- React 应用启动时,会在页面渲染的根元素上绑定原生的 DOM 事件,将该根元素作为委托对象
- 在组件渲染时,会通过 JSX 解析出元素上绑定的事件,并将这些事件与原生事件进行一一映射
- 当用户点击页面元素时,事件会冒泡到根元素,之后根元素监听的事件通过
dispatchEvent方法进行事件派发 dispatchEvent会根据事件的映射关系以及 DOM 元素找到 React 中与之对应的 fiber 节点- 找到 fiber 节点后,将其绑定的合成事件函数加到一个函数执行队列中
- 最后则依次执行队列中的函数完成事件的触发流程
React 17 的事件机制
❗️React 17 中,React 不会再将事件处理添加到 document 上,而是将事件处理添加到渲染 React 树的根 DOM 容器中

React Router
路由懒加载的原理
<Suspense> 配合 React.lazy 实现的路由懒加载。<Suspense> 提供了 加载 -> 过渡 -> 完成后切换 这样一个标准的业务流程
React 捕获到异常之后,会判断这个异常是不是一个 thenable (或者说是不是一个 Promise),如果是的话就会去找父级的 <Suspense>。
<Suspense> 通过 Promise 异步加载代码,一开始会先进入到 fallback,代码加载完成之后再展示对应的组件
<Link> 和 <a> 有什么不同
<Link> 是 React-Router 里实现路由跳转的链接,一般配合 <Route> 使用,React-Router 接管了其默认的链接跳转行为,区别于传统的页面跳转,<Link> 的 ” 跳转 ” 行为只会触发相匹配的 <Route> 对应的页面内容更新,而不会刷新整个页面
// handleClick 部分源码
if (_this.props.onClick) _this.props.onClick(event);
if (!event.defaultPrevented && // onClick prevented default
event.button === 0 && // ignore everything but left clicks
!_this.props.target && // let browser handle "target=_blank" etc.
!isModifiedEvent(event) // ignore clicks with modifier keys
) {
event.preventDefault();
var history = _this.context.router.history;
var _this$props = _this.props,
replace = _this$props.replace,
to = _this$props.to;
if (replace) {
history.replace(to);
} else {
history.push(to);
}
}Link 做了 3 件事情:
- 有
onClick那就执行onClick - click 的时候阻止 a 标签默认事件(这样子点击
<a href="/abc">123</a>就不会跳转和刷新页面) - 再取得跳转 href(即是 to ),用 history(前端路由两种方式 history & hash 之一)跳转(也就意味着会触发 hashchange 事件)。此时只是链接变了,并没有刷新页面
hash 和 history 的异同点和原理
hash
history
新版本 React
React 18 核心新特性
1. 并发模式(Concurrent Mode)
- 可中断渲染:允许 React 在渲染过程中暂停并处理更高优先级的任务(如用户输入),提升交互流畅性。
- API 支持:
useTransition、useDeferredValue用于控制非紧急更新的优先级。
const [isPending, startTransition] = useTransition();
startTransition(() => {
// 非紧急更新(如数据加载)
setNonCriticalState(newValue);
});2. 自动批处理(Automatic Batching)
- 统一批量更新:无论事件类型(如
setTimeout、Promise或原生事件),React 18 默认合并多个setState更新,减少渲染次数。
// React 18 前:异步回调中的 setState 是同步更新
setTimeout(() => {
setCount(1);
setName('A'); // 触发两次渲染
}, 0);
// React 18:自动批处理,仅一次渲染3. 服务端 Suspense 流式渲染
- 流式 HTML:服务端渲染(SSR)支持分段传输 HTML,配合
Suspense实现渐进式加载。 - 选择性注水(Selective Hydration):优先为交互区域注水,缩短可交互时间。
<Suspense fallback={<Spinner />}>
<Comments /> // 延迟加载的组件
</Suspense>4. 新 Hooks
useId:生成跨服务端/客户端的唯一 ID,避免 SSR 与客户端 ID 不匹配。useSyncExternalStore:简化外部状态库(如 Redux)的集成。useInsertionEffect:为 CSS-in-JS 库优化样式注入时机。
React 19(已正式发布)
React 19 已于 2024 年底正式发布,React Compiler、Actions、文档元数据、use 等特性均已落地。主要改进如下:
1. React Server Components 正式支持
- 服务端组件:在服务端渲染静态内容,减少客户端 JS 体积。
- 零捆绑大小:服务端组件不发送 JS 到客户端,提升加载速度。
// ServerComponent.server.js(服务端组件)
export default function ServerComponent() {
return <div>Rendered on Server</div>;
}
// ClientComponent.client.js(客户端组件)
import ServerComponent from './ServerComponent.server';
export default function ClientComponent() {
return <ServerComponent />;
}2. 资源加载优化
- 预加载资源:通过新 API(如
preload)提前加载脚本、样式或数据。
const data = preload('/api/data'); // 提前发起请求3. React Compiler(React Forget)
- 自动记忆化:编译器自动生成
useMemo和useCallback,减少手动优化成本。
// 开发者无需手动优化
const list = items.filter(x => x.active); // 自动记忆化4. Actions API(统一数据处理)
- 简化异步操作:封装数据提交、加载状态和错误处理。
const { submit, isPending } = useAction(async () => {
await saveData(data);
});
return <button onClick={submit} disabled={isPending}>Save</button>;5. 文档元数据支持
- 内置
<title>和<meta>管理:在组件树中直接设置文档头信息。
function Page() {
return (
<>
<title>Home</title>
<meta name="description" content="Welcome" />
<Content />
</>
);
}React 18 vs React 19 对比
| 特性 | React 18 | React 19(预期) |
|---|---|---|
| 并发模式 | ✅ 基础支持 | ✅ 优化扩展(如更细粒度优先级控制) |
| 服务端组件 | ❌ 实验性 | ✅ 正式支持 |
| 自动记忆化 | ❌ 需手动 useMemo | ✅ 编译器自动生成 |
| 资源预加载 | ❌ 需自定义实现 | ✅ 原生 API 支持 |
| 文档元数据管理 | ❌ 需第三方库(如 React Helmet) | ✅ 内置组件支持 |
迁移与适配建议
-
React 18 升级
- 使用
createRoot替代ReactDOM.render。 - 利用
startTransition优化用户体验。
// React 17 ReactDOM.render(<App />, document.getElementById('root')); // React 18 const root = ReactDOM.createRoot(document.getElementById('root')); root.render(<App />); - 使用
-
React 19 前瞻
- 关注服务端组件和编译器进展,逐步适配新范式。
- 使用
use提案处理异步数据(当前需实验版本)。
// 提案中的 use Hook(异步数据读取) const data = use(fetch('/api/data'));
总结
React 18 通过并发模式和流式渲染显著提升了性能与用户体验,而 React 19 将进一步推动服务端组件和自动化优化,降低开发者心智负担。建议优先升级至 React 18,并持续关注 React 19 的动态以把握未来趋势。