Skip to content

React 文档

导读与全书目录表:见同目录 01-React 入门:简介·特性与设计范式.md 开头。


react 底层原理

要点速览

(与目录中 Fiber、JSX、事件、diff、render 各专章一致,此处不重复展开。)

拓展

(可结合 0607 与本文后续小节阅读。)


Fiber 架构

要点速览

shell
React Fiber React 16 引入的基于链表的可中断渲染架构,通过时间切片与优先级调度实现增量渲染,将渲染分为可中断的协调阶段和不可中断的提交阶段,彻底解决主线程长期阻塞问题,提升交互流畅度。

React Fiber React16 推出的可中断协调架构,使用链表结构替代原有同步递归。
核心优势:时间切片、优先级调度、增量渲染,解决主线程阻塞卡顿问题。
维护 current workInProgress Fiber 树,内存中计算 Diff,保证 UI 稳定。
渲染分两阶段:
协调阶段:可中断,遍历 Fiber、Diff、标记副作用
提交阶段:同步不可中断,批量操作 DOM、执行副作用
依靠 Scheduler 做任务优先级管理,高优交互任务优先执行。

流程图

shell
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

拓展

shell
# 什么是 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 一致性,不会出现中间错乱视图

进阶问答

shell
# 讲讲 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 的事件机制

  1. 事件委托: react 17 之前 所有 React 事件(如 onClick、onChange)默认委托到 document 节点。这意味着事件会先冒泡到 document,然后 React 再根据事件目标分发到对应组件这会出现一些问题:如果页面同时使用 React 和非 React 代码(如 jQuery),e.stopPropagation() 可能无法阻止非 React 事件的冒泡,导致意外行为 React 17 及之后,事件委托改为绑定到 React 应用的根 DOM 容器(如 rootNode)避免全局事件污染,适合微前端或多版本 React 共存场景,e.stopPropagation() 只会阻止 React 树内的事件,不会影响全局 document
  2. 事件池 react17 之前 为了性能优化,React 会复用事件对象(事件池),事件回调执行后,事件对象的属性会被清空。如需异步访问事件属性,需调用 e.persist() 问题:异步代码(如 setTimeout)中访问事件对象会失效,除非显式调用 e.persist() react17 之后 移除事件池,每次事件都会创建新的合成事件对象,合成事件不再被复用,异步访问事件属性无需 e.persist()
  3. react 事件,统一不同浏览器的事件差异行为,屏蔽了浏览器差异

react 事件和原生事件的执行顺序

React 版本、冒泡/捕获阶段、是否在根容器监听 有关,不能简化为固定几条「先原生后 React」。实践中以官方行为为准;与原生混用时注意 stopPropagation / stopImmediatePropagation 在合成与原生事件之间的差异

react 事件的常见功能

  1. 获取原生事件
js
// 通过合成事件的 e.nativeEvent
  1. 阻止合成事件冒泡
js
e.stopPropagation();
  1. 阻止同一 DOM 上其它监听器执行(含原生)
js
e.nativeEvent.stopImmediatePropagation();

react 合成事件和原生事件的区别

  1. 命名区别
js
React 事件采用驼峰命名onClick),而非原生的小写onclick)。
  1. 组织默认行为
js
合成事件需显式调用 e.preventDefault()而非返回 false
  1. 兼容性
js
合成事件屏蔽了浏览器差异 IE event.target vs. event.srcElement

性能优化

  • 自动清理:React 在组件卸载时自动移除事件监听,避免内存泄漏。
  • 高效分发:通过事件委托减少实际绑定的监听器数量。

总结

  • react 事件不是浏览器原生事件,而是基于原生事件机制封装的一套自己的事件,所以又称为合成事件
  • react 事件是合成事件,目的是统一不同浏览器的事件差异行为;React 17 前常用事件委托到 document事件池优化;React 17+ 委托到 根容器已移除事件池
  • react 事件触发过程,是通过队列的形式,从触发的组件向父组件回溯,然后调用 JSX 模板中的回调函数 callback
  • React 17 及以前:监听多挂在 documentReact 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 变更,且没有优化措施时