技术干货 # react已发布

React 核心使用范式与底层实现原理

React 作为现代前端开发领头羊框架之一,其设计哲学(声明式、组件化、单向数据流)对前端圈产生了极其深远的影响。要写出高效、健壮的 React 代码,不仅需要熟练掌握 Hooks 的使用范式,更需要深入理解其底层的 Fiber 架构、双缓存机制 以及 Reconciliation(协调算法)

2025.02.236 分钟阅读
React 核心使用范式与底层实现原理
React 核心使用范式与底层实现原理 · Cover

React 作为现代前端开发领头羊框架之一,其设计哲学(声明式、组件化、单向数据流)对前端圈产生了极其深远的影响。要写出高效、健壮的 React 代码,不仅需要熟练掌握 Hooks 的使用范式,更需要深入理解其底层的 Fiber 架构、双缓存机制 以及 Reconciliation(协调算法)。


一、 React 核心使用范式 (Usage & Paradigms)

1. 从 Class 组件到 函数式组件 + Hooks

在 React 16.8 之前,带状态的组件只能通过 Class 组件实现;而在 Hooks 推出后,函数式组件(Functional Components) 成为绝对的主流。

  • 核心理念:UI 是状态的函数,即 UI = f(state)。每次状态更新,函数组件都会重新执行(Re-render)。
import React, { useState, useEffect, useCallback } from 'react';

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);

  // 依赖项变化时触发副作用
  useEffect(() => {
    let isMounted = true;
    fetchUserData(userId).then(data => {
      if (isMounted) setUser(data);
    });
    return () => { isMounted = false; }; // 清理函数
  }, [userId]);

  return (
    <div>
      {user ? <h1>{user.name}</h1> : <p>Loading...</p>}
    </div>
  );
}

2. React Hooks 的核心规则与使用踩坑

使用 Hooks 时必须遵循以下两条硬性规则(由底层实现机制决定):

  1. 只在最顶层调用 Hooks:不要在循环、条件判断(if)或嵌套函数中调用 Hook。
    • 原理:React 底层依赖**链表(LinkedList)**按顺序存储 Hooks 的状态。如果渲染间 Hook 执行顺序改变,链表节点的映射关系就会错乱。
  2. 只在 React 函数组件或自定义 Hook 中调用 Hooks。

3. 性能优化手段:useMemo / useCallback / React.memo

React 默认的渲染机制是:父组件重新渲染,所有子组件默认都会无脑跟着重新渲染。为了避免不必要的计算与 Render:

  • React.memo:高阶组件,用于浅比较(Shallow Compare)组件的 props 是否发生变化,若无变化则跳过子组件渲染。
  • useCallback:缓存函数引用,防止父组件重新渲染时重新生成新的函数引用,导致子组件的 React.memo 失效。
  • useMemo:缓存复杂计算的结果,避免重度计算在每次渲染时重复执行。

二、 React 底层实现原理 (Under the Hood)

1. 为什么需要 Fiber 架构?(React 15 vs React 16+)

React 15 的瓶颈(Stack Reconciler)

在 React 15 及更早版本中,协调过程(Reconciliation)是递归进行的。当组件树非常庞大时,递归更新一旦开始就无法中断。如果主线程(Main Thread)被长任务阻塞超过 16.6ms,就会导致浏览器掉帧,表现为页面卡顿、无法响应用户输入。

React 16+ 的解法(Fiber Reconciler)

React 16 重构了核心架构,引入了 Fiber 链表结构,将同步不可中断的递归过程改为了可中断的异步可调度任务。


2. Fiber 节点的结构与链表设计

Fiber 本质上是一个 JavaScript 对象,代表了一个工作单元(Unit of Work),同时也映射着一个 DOM 节点或组件。

为了能实现“随时暂停、恢复、跳过”工作,Fiber 丢弃了传统的树结构,改用单向链表串联:

function FiberNode(tag, pendingProps, key) {
  // 静态结构属性
  this.tag = tag;             // 组件类型(Function/Class/HostComponent等)
  this.key = key;             // React key
  this.type = null;           // 对应的元素类型 (如 'div' 或 组件函数)
  this.stateNode = null;      // 对应的真实 DOM 节点或 Class 实例

  // Fiber 树结构 (单向链表)
  this.return = null;         // 指向父 Fiber 节点
  this.child = null;          // 指向第一个子 Fiber 节点
  this.sibling = null;        // 指向下一个兄弟 Fiber 节点

  // 状态与副作用
  this.memoizedState = null;  // 当前组件的 State (函数组件中存储 Hooks 链表)
  this.flags = NoFlags;       // 替换原 effectTag,标识需要执行的 DOM 操作 (Placement, Update, Deletion)
  this.alternate = null;      // 指向双缓存树中对应的另一个 Fiber 节点
}
  • 深度优先遍历关系:通过 child 往下找子节点,找不到找 sibling 兄弟节点,再找不到回退到 return 父节点。这种结构让 React 可以随时用循环模拟递归,并在循环中判断时间片(Time Slicing)是否用尽。

3. 双缓存机制 (Double Buffering)

为了避免直接在屏幕显示的 DOM 上频繁修改导致画面闪烁,React 在内存中维护了两棵 Fiber 树:

  1. Current Fiber 树:对应当前屏幕上已经渲染展示的页面。
  2. WorkInProgress (WIP) Fiber 树:正在内存中构建和计算更新的 Fiber 树。
  [屏幕展示]  <--->  Current Fiber 树
                           |  ▲
             alternate 指针 |  | alternate 指针
                           ▼  |
  [内存构建]  <--->  WorkInProgress Fiber 树
  • 切换过程:当 WIP 树在内存中构建完成并完成了所有的 DOM 操作标注后,React 只需要将根节点的 current 指针指向 WIP 树,瞬间完成界面更新(交换指针)。这极大地保证了渲染的稳定性。

4. React 渲染的两大阶段 (Render & Commit)

React 整个渲染流程可以清晰地划分为两个主要阶段:

触发 Update -> 【 Render 阶段 (可中断/异步) 】 -> 【 Commit 阶段 (不可中断/同步) 】

① Render 阶段(协调阶段)

  • 工作内容:遍历 Fiber 树,对比新旧 VDOM(Diff 算法),为需要变更的 Fiber 节点打上 flags 标记(如 Placement 插入、Update 更新、Deletion 删除),构建 WIP Fiber 树。
  • 特点:此阶段在内存中完成,不引发真实 DOM 变更,因此可以被高优先级的任务(如用户点击、输入)中断、打断或重新开始。

② Commit 阶段(提交阶段)

  • 工作内容:读取 Render 阶段产出的带 flags 的 WIP Fiber 树,将其批量应用(Commit)到真实的 DOM 节点上,并执行生命周期钩子或 useEffect / useLayoutEffect。
  • 特点:同步且不可中断,因为一旦开始修改真实 DOM,中断会导致 UI 显示不一致或闪烁。

5. Diff 算法的核心逻辑

React 的 Diff 算法将原本传统树形对比复杂度从 $O(n^3)$ 降低到了 $O(n)$。这得益于其预设的三个同级策略:

  1. 同层比较:Web UI 中跨层级的 DOM 节点移动操作非常少,如果节点跨层级移动,React 会直接销毁重建,而不是尝试复用。
  2. 类型确定:两个不同类型的元素会产生不同的树。如果组件类型改变(如 <div> 变 <p>),直接销毁原树并创建新树。
  3. Key 的唯一性:开发者通过 key 属性标识同级子节点。React 借助 key 进行节点映射匹配,高效判断节点是移动、新增还是删除。

三、 总结对比:Vue 3 与 React 的设计理念差异

维度ReactVue 3
核心哲学函数式编程、Immutable(不可变数据)响应式追踪、Mutable(可变数据)
状态更新驱动显式调用 setState / useState,触发组件自顶向下 Re-render细粒度数据代理(Proxy),自动精准收集依赖并更新 DOM
性能优化方式手动优化(useMemo / useCallback / React.memo)编译期自动优化(静态提升、Patch Flags、Tree shaking)
底层调优重点依靠 Fiber 架构和时间切片解决大树渲染的卡顿问题依靠强大的编译期分析与精细化依赖追踪避免无效渲染

理解了 React 从声明式语法到 Fiber 链表、双缓存以及 Render/Commit 阶段的运作机制,能帮助我们在编写组件时对渲染时机有更深刻的把控,从根本上避开性能坑点。

#react
ZZC

ZZC

写代码,也写一些生活。