React 的底层原理可以从「核心设计目标」「核心机制」「关键技术实现」三个层面拆解,核心是解决「高效更新 UI」和「简化复杂交互逻辑管理」的问题。以下是关键原理的梳理:
一、核心设计目标:为什么需要 React?
传统 DOM 操作存在两个核心问题:
1. 直接操作 DOM 性能差:DOM 是浏览器渲染引擎的接口,频繁操作(如增删改)会触发重排/重绘,消耗性能;
2. 复杂状态与 UI 同步难:当页面状态(如表单输入、接口数据)变化时,需要手动找到对应的 DOM 并更新,逻辑繁琐且易出错。
React 的核心目标:通过「声明式编程」和「高效更新机制」,让开发者只关注“状态对应什么 UI”,而非“如何更新 UI”。
二、核心机制:从「状态变化」到「UI 更新」的完整流程
React 的核心流程可以概括为:状态变化 → 计算 UI 差异 → 最小化更新 DOM,核心依赖 3 个关键技术:「虚拟 DOM」「协调(Reconciliation)算法」「Fiber 架构」。
- 虚拟 DOM(Virtual DOM):UI 的“内存映射”
虚拟 DOM 是 React 解决“直接操作 DOM 性能差”的核心设计。
• 本质:用 JavaScript 对象(通常是树结构)描述真实 DOM 的结构和属性(比如标签名、属性、子元素)。
例:真实 DOM
◦ 避免频繁操作真实 DOM:状态变化时,先在内存中生成新的虚拟 DOM,对比新旧虚拟 DOM 的差异(而非直接操作 DOM);
◦ 跨平台能力:虚拟 DOM 与具体平台(浏览器 DOM、移动端原生组件)无关,React 可以通过不同的“渲染器”(如 ReactDOM、React Native)将虚拟 DOM 转换为对应平台的 UI(比如浏览器 DOM、iOS 原生 View)。
2. 协调(Reconciliation)算法:计算“最小更新差异”
当状态变化时,React 会生成新的虚拟 DOM 树,然后通过「协调算法」对比新旧虚拟 DOM 树,找到需要更新的最小部分(而非全量替换),这个过程也叫“Diff 算法”。
React 的 Diff 算法基于三个“启发式假设”(实际开发中大概率成立),以此简化计算、提升效率:
1. 同层节点类型不同 → 直接销毁旧节点,创建新节点:比如 <div> 变成 <p>,不会对比内部子元素,直接替换;
2. 同类型节点 → 对比属性,复用节点:比如 <div className="a"> 变成 <div className="b">,只更新 className 属性,不重新创建节点;
3. 同列表节点 → 通过 key 标识唯一性:列表渲染时(如 map 生成的节点),如果没有 key 或 key 不稳定(如用索引),React 可能误判节点的增删改,导致不必要的 DOM 操作(甚至状态错乱)。key 的作用是让 React 快速识别“哪些节点是复用的”“哪些是新增/删除的”。
例:列表 [A, B, C] 插入 D 变成 [A, D, B, C],有 key 时 React 能直接定位 D 是新增,B、C 是移动;无 key 时可能误判 B、C 被修改。
- Fiber 架构:解决“渲染阻塞”问题
React 16 之前的渲染是“同步且不可中断”的:如果虚拟 DOM 树很大(比如复杂列表),Diff 计算会占用主线程很长时间(几百毫秒),导致浏览器无法处理用户输入、动画等,出现卡顿。 Fiber 是 React 16 引入的新架构,核心是将渲染过程拆分为“可中断、可恢复、优先级可控”的小单元。
• 本质:Fiber 是一个数据结构(可以理解为“工作单元”),每个 Fiber 对应一个虚拟 DOM 节点,记录该节点的类型、属性、子节点、父节点,以及“当前处理状态”(是否完成、是否需要暂停等)。
• 工作流程:
1. 调度阶段(Scheduler):根据任务优先级(如用户输入 > 动画 > 普通更新)决定先处理哪个任务;
2. 协调阶段(Reconciliation):基于 Fiber 拆分任务(每个 Fiber 对应一个小任务),计算新旧虚拟 DOM 差异(Diff),标记需要更新的节点(如“更新属性”“插入节点”“删除节点”)。这个阶段可以被中断(比如有更高优先级任务插入时,暂停当前任务,保存进度,处理完高优先级任务后再恢复);
3. 提交阶段(Commit):根据协调阶段标记的“更新指令”,一次性操作真实 DOM(这个阶段不可中断,否则会导致 DOM 状态不一致)。
简单说:Fiber 让 React 能“忙里偷闲”——在复杂计算中暂停,先响应用户输入,再继续计算,避免卡顿。
三、状态管理:setState 与 Hooks 的底层逻辑
React 的核心是“状态驱动 UI”,状态变化是触发更新的源头,需要理解状态更新的底层规则。
- setState 的“异步更新”与“批处理”
类组件中 setState 并非立即更新状态,而是将更新请求加入队列,在合适时机(如当前事件循环结束前)批量处理,避免频繁渲染。
例: this.setState({ count: this.state.count + 1 }); this.setState({ count: this.state.count + 1 }); // 最终 count 只 +1(两次更新被合并,基于初始值计算) 如果需要基于“上一次更新后的状态”计算,可以传函数: this.setState(prev => ({ count: prev.count + 1 })); this.setState(prev => ({ count: prev.count + 1 })); // 最终 count +2(基于前一次结果计算) 2. Hooks 的底层实现(以 useState 为例)
函数组件的 Hooks(如 useState useEffect)本质是“状态存储 + 生命周期模拟”,底层依赖两个核心设计:
• Hooks 链表:函数组件每次渲染时,Hooks 的调用顺序是固定的(必须在组件顶层调用,不能在条件/循环中)。React 用一个“链表”存储每个 Hook 的状态(如 useState 的值、更新函数),通过“调用顺序”对应链表节点(第一个 useState 对应链表头,第二个对应下一个节点)。如果调用顺序变化(如条件判断跳过某个 Hook),会导致链表索引错乱,状态读取错误。
• 闭包缓存状态:useState 的更新函数(如 setCount)不会捕获当前渲染的状态,而组件内的变量(如 count)是“当前渲染快照”(闭包保存)。例:
function App() { const [count, setCount] = useState(0); const handleClick = () => { setTimeout(() => { console.log(count); // 点击时的 count(闭包缓存) }, 1000); }; return {count}; } 四、总结:React 底层原理的核心逻辑
1. 虚拟 DOM:用 JS 对象模拟 DOM,避免直接操作 DOM 的性能损耗;
2. Diff 算法:通过类型、属性、key 快速计算差异,最小化 DOM 更新;
3. Fiber 架构:将渲染拆分为可中断的小任务,解决同步渲染阻塞问题;
4. 状态驱动:setState/Hooks 通过队列和闭包管理状态更新,触发 UI 重新计算。
这些设计最终服务于一个目标:让开发者用“声明式”的方式写 UI,同时保证性能和可维护性。