React 文档
导读与全书目录表:见同目录 01-React 入门:简介·特性与设计范式.md 开头。
react 底层原理
要点速览
(与目录中 Fiber、JSX、事件、diff、render 各专章一致,此处不重复展开。)
拓展
(可结合 06、07 与本文后续小节阅读。)
Fiber 架构
要点速览
React Fiber 是 React 16 引入的基于链表的可中断渲染架构,通过时间切片与优先级调度实现增量渲染,将渲染分为可中断的协调阶段和不可中断的提交阶段,彻底解决主线程长期阻塞问题,提升交互流畅度。
React Fiber 是 React16 推出的可中断协调架构,使用链表结构替代原有同步递归。
核心优势:时间切片、优先级调度、增量渲染,解决主线程阻塞卡顿问题。
维护 current 和 workInProgress 双 Fiber 树,内存中计算 Diff,保证 UI 稳定。
渲染分两阶段:
协调阶段:可中断,遍历 Fiber、Diff、标记副作用
提交阶段:同步不可中断,批量操作 DOM、执行副作用
依靠 Scheduler 做任务优先级管理,高优交互任务优先执行。流程图
1. 触发更新(setState / useState / 路由切换 / props变化)
↓
2. Scheduler调度器 接收任务,标记优先级,加入任务队列
↓
3. 开启协调阶段(可中断)
├─ beginWork:深度优先向下遍历,Diff 新旧节点
└─ completeWork:向上回溯,收集DOM副作用、标记effectTag
↓
4. 整棵 Fiber 树遍历完成,锁定所有DOM操作
↓
5. 进入提交阶段(不可中断)
├─ Before Mutation:执行getSnapshotBeforeUpdate
├─ Mutation:真实DOM 新增/删除/修改
└─ Layout:执行 componentDidMount/Update、useLayoutEffect
↓
6. 浏览器绘制(paint)后,异步调度 flushPassiveEffects,执行 **useEffect**(不属于 commit 原子阶段内的同步子步骤)
↓
7. 双树指针交换,workInProgress 变为 current 树拓展
# 什么是 Fiber?
- 是数据结构:链表结构的 Fiber 节点
- 是调度模型:可中断、可恢复、可优先级插队、时间切片
- 核心目标:解决大组件渲染主线程长时间阻塞、页面卡顿
# 为什么抛弃 React15 旧架构?
- 旧架构:同步递归遍历组件树,一旦开始必须一次性执行完毕,不可中断
- 节点量大时,递归耗时过长,阻塞主线程,导致:输入卡顿、动画掉帧、页面卡死
- 无任务优先级,常见交互和低频更新任务串行执行,体验差
# Fiber 三大核心能力?
- 时间切片:将巨型渲染任务拆解为微小 Fiber 单元任务
- 可中断 / 恢复:浏览器空闲执行,有高优任务可暂停、插队
- 增量渲染:内存中分段计算 Diff,最终一次性提交 DOM
# Fiber 单向链表节点结构 + 双缓存机制
单向链表:放弃树形递归,改用单向链表遍历,灵活暂停 / 恢复,遍历顺序:父 → 子 → 兄弟 深度优先遍历
双缓存机制:
- current Fiber:页面当前真实渲染、正在展示的 DOM 对应节点
- workInProgress Fiber:内存中正在构建、计算 Diff 的新节点树
渲染阶段只操作 workInProgress,不影响页面现有 UI
全部计算完成后,指针交换,workInProgress 转正为 current,一次性提交 DOM
避免频繁操作 DOM、防止页面闪烁、支持中断渲染
# Fiber 两大工作阶段(重点)
- 协调阶段(Render 阶段):可中断、异步、时间切片、无 DOM 操作
核心:构建 workInProgress 树、Diff 对比、标记副作用
分为两个动作:beginWork:向下遍历,创建 Fiber、执行组件更新、Props 对比、Diff
completeWork:向上回溯,收集 DOM 属性、缓存副作用
- 提交阶段(Commit 阶段):不可中断、同步、操作真实 DOM
一次性批量执行所有 DOM 增删改
执行类生命周期、**useLayoutEffect**(layout 阶段);**useEffect** 在随后的 passive 阶段异步执行
必须串行执行,保证 UI 一致性,不会出现中间错乱视图进阶问答
# 讲讲 Fiber 架构的作用
React16 重构的协调架构,基于链表结构替代递归树遍历,通过时间切片、优先级调度实现可中断增量渲染,拆分长耗时任务,避免主线程阻塞,优化页面交互流畅度。
# Fiber 架构分为哪两个阶段?各自特点
渲染阶段:可中断、异步、只做内存计算、Diff、不操作 DOM
提交阶段:不可中断、同步、批量操作真实 DOM;**useLayoutEffect** 在此阶段同步执行;**useEffect** 在 commit 之后的 passive 阶段异步执行
# alternate 作用是什么
双缓存核心字段,指向旧的 Fiber 节点,用于新旧树 Diff 对比、复用节点、快速切换视图,提升渲染性能。
# 为什么渲染阶段可以中断,提交不能中断
渲染阶段仅在内存计算,无真实 DOM 变更,中断不会影响 UI
提交阶段直接操作 DOM,一旦中断会导致 DOM 状态错乱、视图撕裂,必须一次性执行完毕
# beginWork 和 completeWork 区别
beginWork:向下遍历,创建 Fiber、组件更新、Diff 逻辑
completeWork:向上回溯,处理 DOM 属性、收集副作用
# 时间切片原理
将大型渲染任务拆分为多个小型 Fiber 单元,利用浏览器空闲时间分片执行,超出时间片则暂停,下次空闲继续执行,保证主线程响应优先。react 中 JSX 语法
JSX 核心
JSX 是 JavaScript + XML 的缩写,在 JSX 文件中我们可以在 JS 语法中编写 HTML 的结构的代码,
本质上是 React.createElement (component, props, ...children) 的语法糖
最终会被 Babel 转译为普通 JavaScript 函数调用
JSX 语法特点
- 只有一个根元素,即:最外层只有一个 div,可用<></>
- 在 {} 内嵌入 JS 表达式,最终会把表达式的结果渲染到 {} 所在位置,函数表达式中不能有 if for 等流程语句,可以使用三元表达式或者数组字符串等返回数据结构的方法
- 标签属性使用小驼峰命名,值用{}花括号表达式,或者字符串引号
JSX 优化
- 避免不必要的渲染:使用 React.memo、useMemo、useCallback
- 列表渲染优化:正确使用 key 属性
- 懒加载组件:使用 React.lazy 和 Suspense
react 的事件机制
- 事件委托: react 17 之前 所有 React 事件(如 onClick、onChange)默认委托到 document 节点。这意味着事件会先冒泡到 document,然后 React 再根据事件目标分发到对应组件这会出现一些问题:如果页面同时使用 React 和非 React 代码(如 jQuery),e.stopPropagation() 可能无法阻止非 React 事件的冒泡,导致意外行为 React 17 及之后,事件委托改为绑定到 React 应用的根 DOM 容器(如 rootNode)避免全局事件污染,适合微前端或多版本 React 共存场景,e.stopPropagation() 只会阻止 React 树内的事件,不会影响全局 document
- 事件池 react17 之前 为了性能优化,React 会复用事件对象(事件池),事件回调执行后,事件对象的属性会被清空。如需异步访问事件属性,需调用 e.persist() 问题:异步代码(如 setTimeout)中访问事件对象会失效,除非显式调用 e.persist() react17 之后 移除事件池,每次事件都会创建新的合成事件对象,合成事件不再被复用,异步访问事件属性无需 e.persist()
- react 事件,统一不同浏览器的事件差异行为,屏蔽了浏览器差异
react 事件和原生事件的执行顺序
与 React 版本、冒泡/捕获阶段、是否在根容器监听 有关,不能简化为固定几条「先原生后 React」。实践中以官方行为为准;与原生混用时注意 stopPropagation / stopImmediatePropagation 在合成与原生事件之间的差异。
react 事件的常见功能
- 获取原生事件
// 通过合成事件的 e.nativeEvent- 阻止合成事件冒泡
e.stopPropagation();- 阻止同一 DOM 上其它监听器执行(含原生)
e.nativeEvent.stopImmediatePropagation();react 合成事件和原生事件的区别
- 命名区别
React 事件采用驼峰命名(onClick),而非原生的小写(onclick)。- 组织默认行为
合成事件需显式调用 e.preventDefault(),而非返回 false- 兼容性
合成事件屏蔽了浏览器差异(如 IE 的 event.target vs. event.srcElement)性能优化
- 自动清理:React 在组件卸载时自动移除事件监听,避免内存泄漏。
- 高效分发:通过事件委托减少实际绑定的监听器数量。
总结
- react 事件不是浏览器原生事件,而是基于原生事件机制封装的一套自己的事件,所以又称为合成事件
- react 事件是合成事件,目的是统一不同浏览器的事件差异行为;React 17 前常用事件委托到 document 与事件池优化;React 17+ 委托到 根容器且已移除事件池
- react 事件触发过程,是通过队列的形式,从触发的组件向父组件回溯,然后调用 JSX 模板中的回调函数 callback
- React 17 及以前:监听多挂在 document;React 17+:监听挂在 应用根 DOM 节点(如
#root),利于多版本 React 与微前端共存
react diff算法
同层比较,从左向右比较、相同保留、不同删除
key 的作用
列表渲染时使用key,目的是优化diff算法,key的作用是判断列表的中元素是新建的还是被移动的元素,从而减小不必要的渲染开销,在列表后面插入一位,key的作用不大,但是要是在前面插入,key的作用很明显 key的作用是标识元素在列表中的位置
使用与不使用 key
- 不使用key:diff算法同级比较,发现不同会将当前层级全部删除,重新创建
- 使用key:只需要移动元素位置即可,相比于创建和删除,移动开销更小些
注意
key必须是唯一值,不可重复、不可用随机数
render 函数
在类组件中 render 是 render 函数,在函数组件中 render 是函数本身
原理
在 render 中编写 jsx --- jsx 通过 babel 编译后会转化成 React.createElement 结构的数据 --- 然后生成虚拟 DOM(新旧树比较,diff 算法,得到最终虚拟 DOM) --- 最终渲染成真实的 DOM
执行时机
类组件中通过 setState 改变状态(不管 state 是否变化,一定触发)函数组件中通过 useState hook 函数 改变状态(只是数据变化时候触发,不变不触发)父组件传递的 props 变更,且没有优化措施时
