React 19 内核探秘

第18章 设计模式与架构决策

作者 杨艺韬 · 10,596 字

第18章 设计模式与架构决策

本章要点

  • React 源码中 10 个核心设计模式的识别与深度剖析:从 Observer 到 Visitor 的工程实践
  • 从 Class 到 Hooks 到 Compiler:三次 API 范式跃迁背后的设计哲学演变
  • Algebraic Effects 与 Prepack:React 团队探索过又放弃的技术方案考古
  • React vs Vue vs Svelte vs Solid:四大框架从响应式模型到编译策略的终极架构对比
  • React 的技术决策方法论:为什么"选择不做什么"比"选择做什么"更重要
  • React 下一个十年的技术趋势:从运行时框架到全栈编译器平台

写完前面十七章,我们已经从 JSX 编译、Fiber 架构、调度器、Reconciliation、Commit 阶段、Hooks 实现、并发模式、Server Components、React Compiler 等各个维度,完成了对 React 19 内核的全面解剖。如果把前面的章节比作"显微镜"——逐行逐函数地观察 React 机体的每一个细胞,那么本章我们需要换一种工具:望远镜

站在足够远的距离回望 React 的源码,你会发现一个令人惊叹的事实:在那些看似复杂的实现细节之下,存在着一组反复出现的设计模式。它们不是教科书上的学术练习,而是 React 团队在十余年工程实践中,面对真实约束做出的真实选择。同时,React 的发展史本身就是一部"技术决策史"——每一次重大版本更新,都意味着一次架构哲学的重新审视。理解这些决策的"为什么",远比记住它们的"是什么"更有价值。

本章是全专栏的收官之章。我们将从设计模式、API 设计哲学、技术考古、框架对比、未来展望五个维度,为你构建一幅 React 架构决策的全景图。这不仅是对前面所有章节的一次高维度总结,更是帮助你建立框架设计者的思维方式——当你下次面对"为什么 React 要这样做"的问题时,你能从第一性原理给出答案。

18.1 React 源码中的 10 个核心设计模式

React 的源码从来不是为了展示设计模式而写的——它是为了解决问题。但当你用设计模式的"棱镜"去观察这些解决方案时,会发现 Gang of Four 书中的经典模式几乎无处不在。以下是 React 源码中最核心的 10 个设计模式,按照它们在渲染流程中出现的顺序排列。

18.1.1 Observer 模式:状态变更的订阅与通知

Observer(观察者)模式是 React 响应式系统的基石。当你调用 setState 时,React 需要知道哪些组件依赖了这个状态,然后通知它们重新渲染。这本质上就是一个发布-订阅关系。

// React 中 Observer 模式的核心体现:useState 的更新链路
// 文件:packages/react-reconciler/src/ReactFiberHooks.js

function dispatchSetState<S, A>(
  fiber: Fiber,
  queue: UpdateQueue<S, A>,
  action: A
): void {
  // 1. 创建 update 对象("事件")
  const update: Update<S, A> = {
    lane,
    revertLane: NoLane,
    action,      // 用户传入的新值或 updater 函数
    hasEagerState: false,
    eagerState: null,
    next: null as any,
  };

  // 2. 将 update 入队("通知"排队)
  const alternate = fiber.alternate;
  if (fiber === currentlyRenderingFiber ||
      (alternate !== null && alternate === currentlyRenderingFiber)) {
    // 渲染阶段的更新,特殊处理
    didScheduleRenderPhaseUpdateDuringThisPass = true;
    // ...
  } else {
    // 3. 调度更新("通知"观察者)
    const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
    if (root !== null) {
      scheduleUpdateOnFiber(root, fiber, lane);  // 触发重新渲染
    }
  }
}

这段代码是经典 Observer 模式的 React 变体。useState 返回的 setState 函数就是"发布"操作,而 scheduleUpdateOnFiber 则是通知调度器"有东西变了,需要重新渲染"。但与传统 Observer 不同的是,React 不直接通知每个组件,而是通知调度器——由调度器决定何时、以何种优先级去"通知"(重新渲染)组件。这是一种间接观察者模式,调度器充当了中介。

深度洞察:Vue 使用的是直接 Observer 模式——每个响应式属性都维护自己的依赖列表,变更时直接通知依赖的 effect。React 则选择了间接模式——任何状态变更都先进入调度器,由调度器统一协调。这两种选择没有绝对优劣,但它们深刻地影响了两个框架的性能特征:Vue 的更新粒度更细(组件级),React 的调度能力更强(可中断、可排优先级)。

18.1.2 Strategy 模式:可插拔的协调策略

Strategy(策略)模式允许算法在运行时被替换。React 的 Reconciler 就是一个巨大的策略容器——它不关心最终的渲染目标是 DOM、Native 组件还是字符串,只负责协调逻辑。

// React Reconciler 的策略注入接口
// 文件:packages/react-reconciler/src/ReactFiberReconciler.js

// HostConfig 就是策略接口——不同的宿主环境提供不同的实现
export type HostConfig = {
  // 创建实例的策略
  createInstance(
    type: string,
    props: Props,
    rootContainerInstance: Container,
    hostContext: HostContext,
    internalInstanceHandle: Object
  ): Instance;

  // 创建文本节点的策略
  createTextInstance(
    text: string,
    rootContainerInstance: Container,
    hostContext: HostContext,
    internalInstanceHandle: Object
  ): TextInstance;

  // 提交更新的策略
  commitUpdate(
    instance: Instance,
    type: string,
    oldProps: Props,
    newProps: Props,
    internalInstanceHandle: Object
  ): void;

  // 添加子节点的策略
  appendChild(parentInstance: Instance, child: Instance | TextInstance): void;

  // ... 还有数十个策略方法
};

React DOM 实现了一套 HostConfig,React Native 实现了另一套,React Three Fiber(3D 渲染)实现了又一套。Reconciler 的核心代码完全不需要改变——这就是 Strategy 模式的威力。

// react-dom 的策略实现
// 文件:packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js

export function createInstance(
  type: string,
  props: Props,
  rootContainerInstance: Container,
  hostContext: HostContext,
  internalInstanceHandle: Object
): Instance {
  const domElement: Instance = createElement(type, props, rootContainerInstance, hostContext);
  precacheFiberNode(internalInstanceHandle, domElement);
  updateFiberProps(domElement, props);
  return domElement;
}

// react-native 的策略实现则完全不同——创建的是 Native View 而不是 DOM Element

18.1.3 Factory 模式:Fiber 节点的创建

Factory(工厂)模式在 React 中最显著的应用是 Fiber 节点的创建。React 需要根据不同的 Element 类型(函数组件、类组件、原生元素、Fragment、Portal 等)创建不同结构的 Fiber 节点。

// Fiber 创建的工厂逻辑
// 文件:packages/react-reconciler/src/ReactFiber.js

export function createFiberFromElement(
  element: ReactElement,
  mode: TypeOfMode,
  lanes: Lanes
): Fiber {
  const type = element.type;
  const key = element.key;
  const props = element.props;

  let fiberTag: WorkTag = FunctionComponent; // 默认为函数组件

  if (typeof type === 'function') {
    // 类组件通过原型上的 isReactComponent 标记识别
    if (shouldConstruct(type)) {
      fiberTag = ClassComponent;
    }
  } else if (typeof type === 'string') {
    // 原生 DOM 元素:div、span、p 等
    fiberTag = HostComponent;
  } else {
    // 特殊类型:Fragment、Suspense、Portal 等
    getTag: switch (type) {
      case REACT_FRAGMENT_TYPE:
        return createFiberFromFragment(props.children, mode, lanes, key);
      case REACT_SUSPENSE_TYPE:
        fiberTag = SuspenseComponent;
        break getTag;
      case REACT_SUSPENSE_LIST_TYPE:
        fiberTag = SuspenseListComponent;
        break getTag;
      // ... 更多类型
    }
  }

  const fiber = createFiber(fiberTag, props, key, mode);
  fiber.elementType = type;
  fiber.type = type;
  fiber.lanes = lanes;
  return fiber;
}

注意这里的类型判定是在创建时刻即时完成的:shouldConstruct 检查 type.prototype?.isReactComponent,函数组件直接被标记为 FunctionComponent。一个有意思的历史注脚:在 React 18 及更早版本中,这里存在一个 IndeterminateComponent(待确定)标签——函数类型的组件先标记为"待确定",直到第一次 beginWork 真正调用它、根据返回值才敲定类型,用以兼容返回 { render() {...} } 对象的 module-pattern 组件。这种"延迟决策"是 Factory 模式的一种变体——工厂不急于确定产品的最终类型,而是留到必要时刻再决定。React 19 删除了 IndeterminateComponent,连同 module-pattern 组件的支持一并移除,工厂回归了最直接的形态:创建时就确定产品类型。

18.1.4 Visitor 模式:Fiber 树的遍历

Visitor(访问者)模式允许你在不修改数据结构的情况下,为其添加新的操作。React 的 beginWorkcompleteWork 就是 Fiber 树上的两个"访问者"——它们遍历 Fiber 树,但对每种类型的 Fiber 节点执行不同的操作。

// beginWork 本质上是一个 Visitor 的 visit 方法
// 文件:packages/react-reconciler/src/ReactFiberBeginWork.js

function beginWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes
): Fiber | null {
  // 根据 Fiber 节点的 tag(类型),分派到不同的处理函数
  switch (workInProgress.tag) {
    case FunctionComponent:
      return updateFunctionComponent(current, workInProgress, /*...*/);
    case ClassComponent:
      return updateClassComponent(current, workInProgress, /*...*/);
    case HostComponent:
      return updateHostComponent(current, workInProgress, /*...*/);
    case SuspenseComponent:
      return updateSuspenseComponent(current, workInProgress, /*...*/);
    case MemoComponent:
      return updateMemoComponent(current, workInProgress, /*...*/);
    case ForwardRef:
      return updateForwardRef(current, workInProgress, /*...*/);
    case LazyComponent:
      return mountLazyComponent(current, workInProgress, /*...*/);
    // ... 20+ 种节点类型
  }
}

这个巨大的 switch 语句就是 Visitor 模式的特征标志。每种 Fiber 类型定义了自己被"访问"时的行为,而遍历逻辑(workLoopperformUnitOfWorkbeginWork/completeWork)与节点处理逻辑完全分离。

18.1.5 Command 模式:Update 与 Effect

Command(命令)模式将操作封装为对象,使得操作可以排队、撤销、重放。React 中有两个经典的 Command 模式应用:

Update 对象——每一次 setState 调用都被封装为一个 Update 命令:

// Update 就是一个 Command 对象
type Update<S, A> = {
  lane: Lane;               // 优先级
  revertLane: Lane;          // 回退优先级(用于 useOptimistic)
  action: A;                 // 命令的载荷
  hasEagerState: boolean;    // 是否已提前计算
  eagerState: S | null;      // 提前计算的结果
  next: Update<S, A> | null; // 链表下一个命令
};

Effect 对象——useEffectuseLayoutEffect 产生的副作用也是 Command。React 19 把 cleanup 从 Effect 顶层挪到了共享的 inst 上(同一 effect 在 current / workInProgress 之间复用同一实例,才能正确保存 destroy):

// ReactFiberHooks.js(v19)
type EffectInstance = {
  destroy: void | (() => void), // 撤销命令(cleanup)存在 inst 上
};

type Effect = {
  tag: HookFlags;                       // 副作用类型标记
  create: () => (() => void) | void;    // 执行命令
  inst: EffectInstance;                 // 跨渲染共享;destroy 写在这里
  deps: Array<mixed> | null;            // 依赖条件
  next: Effect;                         // 循环链表下一个命令
};
// 对比:React ≤18 曾把 destroy 直接挂在 Effect 顶层字段上

这些 Command 对象被创建后不会立即执行,而是排入队列,等待 Reconciler 在合适的时机统一处理。这使得 React 能够实现批量更新(多个 setState 合并为一次渲染)和优先级调度(高优先级的 Update 先处理)。

18.1.6 Mediator 模式:Scheduler 作为中央调度者

Mediator(中介者)模式定义一个中介对象来封装一系列对象之间的交互。React 的 Scheduler 就是整个运行时的 Mediator——所有的更新请求都经过它,由它决定执行顺序和时机。

// Scheduler 作为 Mediator 的角色
// 文件:packages/scheduler/src/forks/Scheduler.js

function unstable_scheduleCallback(
  priorityLevel: PriorityLevel,
  callback: Callback,
  options?: { delay: number }
): Task {
  const currentTime = getCurrentTime();
  const startTime = typeof options === 'object' && options !== null
    ? currentTime + options.delay
    : currentTime;

  // 根据优先级计算超时时间
  let timeout: number;
  switch (priorityLevel) {
    case ImmediatePriority:   timeout = -1; break;
    case UserBlockingPriority: timeout = 250; break;
    case NormalPriority:       timeout = 5000; break;
    case LowPriority:         timeout = 10000; break;
    case IdlePriority:        timeout = maxSigned31BitInt; break;
  }

  const expirationTime = startTime + timeout;

  const newTask: Task = {
    id: taskIdCounter++,
    callback,
    priorityLevel,
    startTime,
    expirationTime,
    sortIndex: -1,
  };

  // 根据开始时间分配到不同队列
  if (startTime > currentTime) {
    // 延迟任务 → timerQueue(小顶堆)
    newTask.sortIndex = startTime;
    push(timerQueue, newTask);
  } else {
    // 即时任务 → taskQueue(小顶堆)
    newTask.sortIndex = expirationTime;
    push(taskQueue, newTask);
  }

  // 请求调度
  if (!isHostCallbackScheduled && !isPerformingWork) {
    isHostCallbackScheduled = true;
    requestHostCallback();
  }

  return newTask;
}

Scheduler 不关心更新从哪来(用户交互?网络请求?定时器?),也不关心更新要做什么(重新渲染?提交 DOM?执行 Effect?)。它只负责根据优先级和时间约束,决定"什么时候执行什么"。所有模块都通过 Scheduler 这个 Mediator 进行间接通信。

18.1.7 Memento 模式:双缓冲与状态快照

Memento(备忘录)模式在不暴露对象实现细节的情况下保存和恢复对象的状态。React 的双缓冲树(current/workInProgress)就是 Memento 模式的应用——current 树保存着当前屏幕上的状态"快照",而 workInProgress 树是正在计算的新状态。

// 双缓冲切换:Memento 的"恢复"操作
// 文件:packages/react-reconciler/src/ReactFiberWorkLoop.js

function commitRootImpl(root: FiberRoot, /*...*/) {
  // ... 执行各种 DOM 操作

  // 关键:将 workInProgress 树"提升"为新的 current 树
  // 旧的 current 树变成下一轮的 workInProgress 模板
  root.current = finishedWork;

  // 如果渲染过程中出错,可以"回退"到 current 树——这就是 Memento 的恢复能力
}

当并发渲染被更高优先级的更新中断时,React 可以丢弃未完成的 workInProgress 树,"恢复"到 current 树的状态。这种能力正是 Memento 模式赋予的。

18.1.8 Composite 模式:组件树的递归结构

Composite(组合)模式将对象组合成树形结构以表示"部分-整体"的层次结构。React 的组件模型天然就是 Composite 模式——每个组件既可以是叶子节点(如 <input />),也可以是容器节点(如 <div>{children}</div>),而 React 用完全一致的方式处理它们。

// Composite 模式的用户空间体现
function App() {
  return (
    <Layout>           {/* 容器 */}
      <Header />       {/* 叶子 */}
      <Content>        {/* 容器 */}
        <Article />    {/* 叶子 */}
        <Sidebar />    {/* 叶子 */}
      </Content>
      <Footer />       {/* 叶子 */}
    </Layout>
  );
}
// Layout、Content 是容器节点,Header、Article、Sidebar、Footer 是叶子节点
// 但在 Fiber 树中,它们都是 Fiber 节点,通过 child/sibling/return 链接

18.1.9 Iterator 模式:Hooks 链表的遍历

Iterator(迭代器)模式提供一种方式来顺序访问聚合对象中的元素。React 的 Hooks 链表就使用了 Iterator 模式——每个 Hook 调用会推进"游标"到下一个节点:

// Hooks 链表的迭代机制
// 文件:packages/react-reconciler/src/ReactFiberHooks.js

let currentHook: Hook | null = null;        // 当前正在处理的 Hook
let workInProgressHook: Hook | null = null; // 正在构建的 Hook

function updateWorkInProgressHook(): Hook {
  // "迭代"到下一个 Hook
  let nextCurrentHook: Hook | null;

  if (currentHook === null) {
    // 首个 Hook:从 Fiber 的 memoizedState 开始
    const current = currentlyRenderingFiber.alternate;
    nextCurrentHook = current !== null ? current.memoizedState : null;
  } else {
    // 后续 Hook:移动到链表的下一个节点
    nextCurrentHook = currentHook.next;
  }

  currentHook = nextCurrentHook;

  // 克隆为 workInProgress Hook
  const newHook: Hook = {
    memoizedState: currentHook.memoizedState,
    baseState: currentHook.baseState,
    baseQueue: currentHook.baseQueue,
    queue: currentHook.queue,
    next: null,
  };

  if (workInProgressHook === null) {
    currentlyRenderingFiber.memoizedState = newHook;
    workInProgressHook = newHook;
  } else {
    workInProgressHook.next = newHook;
    workInProgressHook = newHook;
  }

  return workInProgressHook;
}

这就是为什么 Hooks 不能在条件语句中调用——迭代器必须按照固定顺序遍历,否则"游标位置"就会错乱。

18.1.10 Proxy 模式:Ref 与 Lazy 的延迟访问

Proxy(代理)模式为另一个对象提供一个替身以控制对它的访问。React.lazyuseRef 都是 Proxy 模式的体现。

// React.lazy:Proxy 模式的经典应用
// 文件:packages/react/src/ReactLazy.js

export function lazy<T>(
  ctor: () => Thenable<{ default: T }>
): LazyComponent<T, Payload<T>> {
  const payload: Payload<T> = {
    _status: Uninitialized,
    _result: ctor,  // 存储工厂函数,而不是实际组件
  };

  const lazyType: LazyComponent<T, Payload<T>> = {
    $$typeof: REACT_LAZY_TYPE,
    _payload: payload,
    _init: lazyInitializer,  // 首次访问时才初始化
  };

  return lazyType;
}

function lazyInitializer<T>(payload: Payload<T>): T {
  if (payload._status === Uninitialized) {
    const ctor = payload._result;
    const thenable = ctor();  // 此时才真正加载
    // ...
    payload._status = Pending;
    payload._result = thenable;
  }
  // ...
}

React.lazy 创建了一个"代理"组件,它在第一次被渲染之前不会加载真正的组件代码。这种延迟加载的行为就是 Proxy 模式的核心价值。

深度洞察:如果你仔细观察以上 10 个模式,会发现它们不是孤立存在的——它们构成了一个"模式系统"。Observer 触发更新,Command 封装更新,Mediator 调度更新,Strategy 执行更新,Visitor 遍历树,Factory 创建节点,Composite 组织树结构,Iterator 遍历 Hooks,Memento 保存快照,Proxy 延迟加载。整个 React 渲染流程就是这 10 个模式的协奏曲。

18.2 从 Class 到 Hooks 到 Compiler:API 设计哲学的三次演进

graph LR
    C["2013-2018\nClass 组件\n面向对象"] -->|"逻辑复用困难\nmixin 反模式"| H["2019-2024\nHooks\n函数式组合"]
    H -->|"手动 memo 负担\n性能心智模型重"| Co["2025+\nCompiler\n自动优化"]
    style C fill:#fee2e2,stroke:#ef4444
    style H fill:#fef3c7,stroke:#f59e0b
    style Co fill:#dcfce7,stroke:#22c55e

React 的 API 不是一成不变的。从 2013 年开源至今,React 经历了三次根本性的 API 范式转变。每一次转变都不仅仅是语法的改变,更是设计哲学的重新定义。

18.2.1 Class 时代(2013-2018):面向对象的组件模型

React 最初的 API 设计深受面向对象思想的影响。一个组件就是一个类,状态封装在实例中,生命周期方法定义了组件在不同阶段的行为。

// Class 组件:面向对象的设计范式
class UserProfile extends React.Component<Props, State> {
  state = { user: null, loading: true };

  // 生命周期方法——模拟"对象生命历程"
  componentDidMount() {
    this.fetchUser();
  }

  componentDidUpdate(prevProps: Props) {
    if (prevProps.userId !== this.props.userId) {
      this.fetchUser();
    }
  }

  componentWillUnmount() {
    this.abortController?.abort();
  }

  // 方法——对象的行为
  async fetchUser() {
    this.abortController = new AbortController();
    this.setState({ loading: true });
    const user = await fetchUserById(
      this.props.userId,
      this.abortController.signal
    );
    this.setState({ user, loading: false });
  }

  render() {
    const { user, loading } = this.state;
    if (loading) return <Spinner />;
    return <div>{user.name}</div>;
  }
}

Class 组件的核心设计理念是:组件是一个有状态的对象,它有明确的生命周期阶段。 这种模型直观、容易理解,但有三个深层问题:

  1. 逻辑碎片化:数据获取的逻辑分散在 componentDidMountcomponentDidUpdatecomponentWillUnmount 三个方法中,难以作为整体理解和复用。
  2. this 的困境:JavaScript 的 this 在回调函数中的行为与直觉不符,导致了大量的 .bind(this) 和箭头函数属性。
  3. 复用困难:高阶组件(HOC)和 Render Props 虽然能实现逻辑复用,但会导致"wrapper hell"——组件层级深度膨胀。

18.2.2 Hooks 时代(2018-2024):代数效应的启示

2018 年 React Conf 上,Dan Abramov 用一场现场编码演示引入了 Hooks。这不是一次渐进式改进,而是一次范式革命——从面向对象到函数式 + 效应式

// 同样的功能,用 Hooks 实现
function UserProfile({ userId }: { userId: string }) {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const abortController = new AbortController();
    setLoading(true);

    fetchUserById(userId, abortController.signal)
      .then(user => {
        setUser(user);
        setLoading(false);
      });

    return () => abortController.abort();  // cleanup 与 setup 在一起
  }, [userId]);  // 依赖声明——"当 userId 变化时重新执行"

  if (loading) return <Spinner />;
  return <div>{user.name}</div>;
}

Hooks 的设计理念转变可以用一句话概括:组件不是对象,而是一个从数据到 UI 的函数,副作用是这个函数的"附带效应"。

这个理念的灵感来源之一是 Algebraic Effects(代数效应)——一种来自编程语言理论的概念。代数效应允许函数"声明"它需要某种能力(如读取状态、执行副作用),而将这些能力的"实现"交给调用者。Dan Abramov 在 2019 年的博客文章 "Algebraic Effects for the Rest of Us" 中明确阐述了这种联系:

// 代数效应的伪代码(不是有效的 JavaScript)
function UserProfile({ userId }) {
  // "perform" 声明了一个效应——"我需要状态"
  const [user, setUser] = perform useState(null);

  // "perform" 声明了另一个效应——"我需要副作用"
  perform useEffect(() => {
    fetchUserById(userId).then(setUser);
  }, [userId]);

  return <div>{user.name}</div>;
}

// React 运行时是"handler"——它"处理"这些效应
// 不同的渲染上下文(客户端/服务端/测试)可以提供不同的 handler

虽然 JavaScript 没有原生的代数效应支持,React 团队通过 Hooks 的链表机制模拟了这种效果。Hooks 的"规则"(不能条件调用、必须在顶层调用)就是这种模拟的代价——真正的代数效应不需要这些限制。

18.2.3 Compiler 时代(2024-):心智模型的解放

React Compiler 代表了第三次范式转变。它的核心洞察是:开发者不应该需要思考"如何优化",优化应该是编译器的工作。

// Compiler 之前:开发者需要手动优化
function ProductList({ products, category }: Props) {
  // 需要 useMemo 避免每次渲染都重新过滤
  const filteredProducts = useMemo(
    () => products.filter(p => p.category === category),
    [products, category]
  );

  // 需要 useCallback 避免传给子组件的函数引用变化
  const handleClick = useCallback(
    (id: string) => { navigate(`/product/${id}`); },
    [navigate]
  );

  return filteredProducts.map(p => (
    // 需要 React.memo 避免不必要的子组件重渲染
    <ProductCard key={p.id} product={p} onClick={handleClick} />
  ));
}

const ProductCard = React.memo(({ product, onClick }: CardProps) => (
  <div onClick={() => onClick(product.id)}>{product.name}</div>
));
// Compiler 之后:相同的代码,但不需要手动优化
function ProductList({ products, category }: Props) {
  // 编译器会自动分析依赖并缓存
  const filteredProducts = products.filter(p => p.category === category);

  const handleClick = (id: string) => {
    navigate(`/product/${id}`);
  };

  return filteredProducts.map(p => (
    <ProductCard key={p.id} product={p} onClick={handleClick} />
  ));
}

// 编译器自动注入等效的 memo 行为
function ProductCard({ product, onClick }: CardProps) {
  return <div onClick={() => onClick(product.id)}>{product.name}</div>;
}

三次范式演进的对比:

维度 Class 时代 Hooks 时代 Compiler 时代
核心抽象 组件是对象 组件是函数 组件是声明
状态管理 实例属性 (this.state) 闭包捕获 (useState) 自动记忆化
逻辑复用 HOC / Render Props 自定义 Hooks 自定义 Hooks + 零样板
性能优化 shouldComponentUpdate useMemo / useCallback / memo 编译器自动完成
心智负担 高(生命周期、this) 中(依赖数组、规则) 低(直觉编写)
编译时参与 最小(JSX 转换) 深度(依赖分析、记忆化注入)

深度洞察:三次演进有一个共同方向——减少开发者需要关心的"附带复杂度"。Class 时代,你需要理解 this、生命周期、HOC 的工作原理。Hooks 时代,你需要理解依赖数组、闭包陷阱、memo 优化。Compiler 时代,你只需要理解 React 的基本规则——"组件是纯函数"——编译器会处理其余一切。React 的终极目标不是让框架更强大,而是让框架"消失"。

18.3 React 的技术决策考古:被放弃的方案

React 的历史不仅是"做了什么"的历史,也是"没做什么"的历史。那些被探索过又被放弃的技术方案,往往比最终选择更能揭示 React 团队的决策逻辑。

18.3.1 Algebraic Effects:Hooks 的理想与现实

如前所述,Hooks 的设计灵感部分来自 Algebraic Effects。但为什么 React 没有直接使用或实现 Algebraic Effects?

Algebraic Effects 的核心思想是:一个函数可以"抛出"一个效应(effect),运行时会捕获这个效应,执行相应的处理,然后恢复函数的执行。这与异常处理类似,但关键区别是:异常是单向的(抛出后不会回到抛出点),而效应是双向的(处理完后会继续执行抛出点之后的代码)。

// 假设 JavaScript 支持 Algebraic Effects(假想语法)
function UserProfile({ userId }) {
  // "perform" 抛出一个效应,React 的 handler 会处理它
  // 处理完成后,结果被"注入"回来,函数继续执行
  const user = perform ReadUser(userId);    // 可恢复的效应
  const theme = perform ReadContext(Theme);  // 可恢复的效应

  // 如果这里是条件语句中的 perform,完全没问题
  if (user.isPremium) {
    const premiumTheme = perform ReadContext(PremiumTheme);
    return <PremiumView theme={premiumTheme} user={user} />;
  }

  return <StandardView theme={theme} user={user} />;
}

如果 JavaScript 原生支持 Algebraic Effects,React 不需要 Hooks 链表、不需要"规则"(不能条件调用)、不需要闭包陷阱——所有这些问题都是因为 Hooks 是 Algebraic Effects 的一种不完美模拟

React 团队没有选择在 JavaScript 中实现真正的 Algebraic Effects,原因有三:

  1. 语言层面不可行:JavaScript 没有 one-shot delimited continuations,无法实现可恢复的效应处理。Generator 函数虽然可以暂停和恢复,但它们的语法限制太多,无法透明地用于组件。

  2. 性能开销:即使能实现,模拟 continuations 需要拷贝调用栈的状态,在频繁渲染的场景下开销不可接受。

  3. 生态兼容性:一种全新的执行模型会使得所有现有的 JavaScript 工具(调试器、性能分析器、错误追踪工具)无法正常工作。

React 选择了一种务实的折中:用链表模拟效应的顺序执行,用闭包捕获状态。代价是"Hooks 规则",但收益是与现有 JavaScript 生态的完全兼容。

18.3.2 Prepack:死代码消除的超前尝试

2017-2019 年间,Meta 开发了一个名为 Prepack 的工具,目标是通过部分求值(Partial Evaluation) 在编译时执行 JavaScript 代码,消除运行时开销。React 团队曾认真考虑将 Prepack 集成到 React 的编译管线中。

// Prepack 的工作原理示意

// 输入代码
function getGreeting(name: string) {
  const prefix = "Hello";
  const separator = ", ";
  const suffix = "!";
  return prefix + separator + name + suffix;
}

const message = getGreeting("World");

// Prepack 编译后
const message = "Hello, World!";
// Prepack 在编译时"执行"了函数,直接产出结果

对于 React 组件,Prepack 的愿景是:

// 输入:一个使用常量 props 的组件
function StaticBanner() {
  const config = { color: "blue", size: "large" };
  return (
    <div style={{ color: config.color }}>
      <span className={`banner-${config.size}`}>Welcome</span>
    </div>
  );
}

// Prepack 理论上的输出:跳过所有中间计算
function StaticBanner() {
  return {
    $$typeof: Symbol.for('react.element'),
    type: 'div',
    props: {
      style: { color: 'blue' },
      children: {
        $$typeof: Symbol.for('react.element'),
        type: 'span',
        props: {
          className: 'banner-large',
          children: 'Welcome'
        }
      }
    }
  };
}

Prepack 最终在 2019 年被停止开发,原因包括:

  1. 复杂度爆炸:对于包含副作用、闭包、异步操作的真实代码,部分求值的分析复杂度呈指数增长。React 组件几乎不可能是纯静态的——它们依赖 props、state、context,这些都是运行时才能确定的值。

  2. 收益有限:对于大多数 React 应用,性能瓶颈不在于组件函数的执行开销,而在于不必要的重渲染。Prepack 优化的是"函数执行速度",但 React 真正需要优化的是"渲染频率"。

  3. 维护成本:Prepack 需要实现一个完整的 JavaScript 语义解释器,跟上 ECMAScript 标准的演进速度是一个巨大的工程负担。

React Compiler 吸取了 Prepack 的教训。它不试图"执行"代码,而是分析代码的依赖关系,自动注入记忆化。这是一个范围更窄但更可行的目标——你不需要理解代码"做了什么",只需要理解"什么变了"。

18.3.3 其他被放弃的技术方案

React Fire(2018):一个计划重写 React DOM 事件系统的项目,目标是让 React 的事件行为更接近原生 DOM 事件。项目最终没有以独立版本发布,但其部分成果(如将事件委托从 document 移到 root)被整合进了 React 17。

React Flare(2019):一个实验性的高级事件处理系统,目标是提供跨平台的事件抽象(如 Press、Hover、Focus),使得同一套事件代码可以在 Web 和 React Native 上运行。项目因复杂度和维护负担被放弃。

Concurrent Mode "全有或全无"(2019-2020):React 最初计划以一种"开关式"的方式启用并发模式——要么整个应用是并发的,要么不是。后来发现这对于大型应用的渐进式迁移是灾难性的,于是改为了通过 startTransitionuseDeferredValue 等 API 逐步开启并发特性。

深度洞察:React 团队放弃一个技术方案的速度,往往和他们探索一个技术方案的热情一样令人印象深刻。这种"快速原型、快速失败"的方法论,使得 React 能够在"正确但昂贵的解决方案"和"可行且实用的解决方案"之间做出明智的取舍。Prepack 教会了他们"编译时优化应该做什么",Algebraic Effects 教会了他们"函数式组件应该是什么样子"——即使这两个项目都没有直接变成产品。

18.4 React vs Vue vs Svelte vs Solid:四大框架的终极架构对比

graph TD
    subgraph React["React 19"]
        R1["Virtual DOM\nFiber 架构"]
        R2["Pull-based\n调度器拉取更新"]
        R3["运行时重\n编译时轻(Compiler)"]
    end
    subgraph Vue["Vue 3"]
        V1["Virtual DOM\n+ Proxy 响应式"]
        V2["Push-based\n依赖追踪自动触发"]
        V3["编译优化\n(Block Tree)"]
    end
    subgraph Svelte["Svelte 5"]
        S1["无 Virtual DOM\n编译时生成 DOM 操作"]
        S2["Push-based\nRune 信号系统"]
        S3["编译时重\n运行时极轻"]
    end
    subgraph Solid["Solid"]
        O1["无 Virtual DOM\n细粒度响应式"]
        O2["Push-based\n信号驱动"]
        O3["编译时优化\n+ 运行时信号"]
    end
    style React fill:#dbeafe,stroke:#3b82f6
    style Vue fill:#dcfce7,stroke:#22c55e
    style Svelte fill:#fee2e2,stroke:#ef4444
    style Solid fill:#fef3c7,stroke:#f59e0b

站在 2026 年的时间点回望,前端框架的竞争格局已经从"哪个更快"演变为"哪种架构理念更能经受时间的考验"。以下是四大框架在核心架构层面的系统对比。

18.4.1 响应式模型:推与拉的哲学之争

四大框架在响应式模型上的选择,决定了它们几乎所有其他设计决策。

React:Pull-based(拉模型)

// React 的更新是"拉"的——组件被调度器"拉"去重新渲染
function Counter() {
  const [count, setCount] = useState(0);

  // 当 setCount 被调用时,React 不会立即更新
  // 而是标记组件"脏",然后在下一个调度周期"拉"组件重新渲染
  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
// React 重新执行整个函数,通过 diff 找出变化——"不知道什么变了,所以全部重新算"

Vue:Push-based(推模型)

// Vue 的更新是"推"的——响应式系统直接推送变更到依赖的 effect
import { ref, watchEffect } from 'vue';

const count = ref(0);

// Vue 在 watchEffect 执行时自动收集依赖
// 当 count.value 变化时,Vue 知道"精确地哪些 effect 需要重新运行"
watchEffect(() => {
  document.title = `Count: ${count.value}`;
});
// Vue 使用 Proxy 拦截属性访问,建立精确的依赖关系图

Svelte:Compile-time Push(编译时推模型)

// Svelte 5(runes):源码侧是 $state / $derived,
// 编译器落到 signal 运行时,而不是 Svelte 3/4 的 dirty-bitmask
// 源代码
let count = $state(0);
let doubled = $derived(count * 2);

// 编译后(示意:Svelte 5 signal 风格)
// 运行时提供 source / derived / effect 等原语
const count = source(0);
const doubled = derived(() => get(count) * 2);

effect(() => {
  set_text(t0, get(count));     // 精确订阅 count
  set_text(t1, get(doubled));   // 精确订阅 doubled——不经过 VDOM
});

// 对比:Svelte 3/4 编译产物是 dirty bitmask + if (dirty & …) 分支,
// 例如 if ($$self.$$.dirty & /* count */ 1) { … }——那是旧代模型。

Solid:Fine-grained Push(细粒度推模型)

// Solid 使用信号(Signal)实现细粒度推更新
import { createSignal, createEffect } from 'solid-js';

function Counter() {
  const [count, setCount] = createSignal(0);

  // 这个组件函数只执行一次!
  // createEffect 建立了 count → DOM 的直接绑定
  createEffect(() => {
    document.title = `Count: ${count()}`;
  });

  // JSX 中的表达式被编译为响应式绑定,而不是每次都重新执行整个函数
  return <button onClick={() => setCount(c => c + 1)}>{count()}</button>;
}

18.4.2 架构对比全景表

维度 React Vue 3 Svelte 5 Solid
响应式模型 Pull(调度器拉取) Push(Proxy 推送) 编译时推送 细粒度 Signal 推送
虚拟 DOM 有(Fiber 树) 有(VNode 树) 无(编译为命令式) 无(细粒度绑定)
组件更新粒度 组件级(整个函数重执行) 组件级(模板级优化) 语句级(编译时分析) 表达式级(Signal 追踪)
编译器角色 深度(Compiler 自动 memo) 中度(模板优化、静态提升) 核心(运行时极小) 中度(JSX 转响应式绑定)
运行时大小 ~60KB+ (gzipped,react+react-dom client) ~34–38KB (gzipped) 十余 KB 级 (gzipped,Svelte 5 signal 运行时;非 ~2KB) ~7KB (gzipped)
并发渲染 原生支持(Scheduler) 不支持 不支持 不支持
Server Components 原生支持 实验性 不支持 实验性
状态管理 外部库为主 内建响应式系统 内建 runes 内建 Signal
TypeScript 支持 原生(类型体操友好) 优秀(模板类型检查) 良好 优秀
学习曲线 陡峭(并发模型复杂) 适中 平缓 适中
生态系统规模 最大

体积测量口径(约 2025–2026):上表 gzip 为生产 client 运行时的量级,不是 npm 包整包、也不是 dev 构建。React 19 以 react + react-dom client 生产路径 minify+gzip 计,约 60KB+(React 18 时代常见的 ~40KB 已不适用);Vue 3 完整运行时约三十余 KB;Svelte 5 因引入 signal 运行时(svelte/internal/client)基线升到十余 KB 级,旧文常说的 Svelte ~2KB 对应的是 Svelte 3/4 极简产物;Solid 仍约数 KB。具体数字随 tree-shaking、entry 与压缩参数浮动,比较时请统一口径。

18.4.3 Diff 策略对比

四大框架在处理列表更新时的 Diff 策略差异显著:

// React:两轮遍历 + Map 查找
// 时间复杂度:O(n),但常数因子较大
// 特点:优先处理位置不变的节点,再处理移动的节点

// Vue 3:前缀/后缀同步 + 中间乱序区求最长递增子序列
// 时间复杂度:O(n log n)(LIS 部分),但实际 DOM 操作次数更少
// 特点:先从头、尾两侧跳过未变化的节点,再对中间乱序区
// 用 LIS 找出无需移动的最长稳定序列,最小化 DOM 移动
// ("双端四向比较"是 Vue 2 的算法,Vue 3 已改用上述策略)

// Svelte 5:编译时生成的命令式更新
// 不需要传统意义上的 Diff——编译器已经知道什么会变
// 对于动态列表仍需运行时 keyed each 块

// Solid:无 Diff
// 依赖细粒度 Signal,每个列表项独立追踪
// <For> 组件直接管理 DOM 节点的增删改

18.4.4 各框架的"甜蜜区"

每个框架都有它最擅长的场景。理解这一点比争论"谁更好"有价值得多。

React 的甜蜜区

  • 大型团队协作的复杂应用(强类型、规范化的 API 降低沟通成本)
  • 需要并发渲染能力的场景(如包含大量实时交互的 dashboard)
  • 全栈 React 场景(Server Components + Server Actions)
  • 需要跨平台渲染的项目(React Native、Three.js 等)

Vue 的甜蜜区

  • 中等复杂度的应用(内建的响应式系统减少了外部依赖)
  • 渐进式采用的场景(可以从一个 <script> 标签开始)
  • 模板偏好的团队(模板比 JSX 更容易进行静态分析和优化)

Svelte 的甜蜜区

  • 对 bundle 大小极度敏感的场景(如嵌入式 widget、弱网环境)
  • 中小型应用(编译器消除了大量运行时开销)
  • 追求极致性能的静态内容站点

Solid 的甜蜜区

  • 对更新性能要求极高的场景(如实时数据可视化、游戏 UI)
  • 不需要并发渲染但需要极致响应速度的应用
  • 偏好 JSX 但希望获得细粒度响应式的开发者

深度洞察:四大框架的核心分歧在于一个根本性的问题——谁负责追踪变化? React 的回答是"没人追踪,我全部重新算"(然后用 Diff 和 Compiler 来弥补性能)。Vue 的回答是"运行时 Proxy 自动追踪"。Svelte 的回答是"编译器在构建时就知道什么会变"。Solid 的回答是"Signal 在运行时精确追踪到表达式级别"。没有哪种选择是绝对正确的——每一种都是在"性能"、"开发体验"、"灵活性"三角中做出的不同权衡。React 选择牺牲初始运行时性能,换取调度的灵活性和生态的广泛性——这个选择在"大型复杂应用"的场景下被证明是极其成功的。

18.5 展望:React 的下一个十年

预测技术的未来是危险的,但我们可以从 React 团队的公开言论、RFC、实验性分支和已有的技术轨迹中,推断出几个高确信度的方向。

18.5.1 React Compiler 的全面成熟

React Compiler 在 2024 年以 "React Compiler Beta" 的形式进入公众视野,截至 2026 年初已经在 Meta 的多个大型产品(Instagram、Facebook)中得到验证。它的下一步发展方向包括:

更深层的静态分析:当前的 Compiler 主要进行"自动记忆化"——识别不需要重新计算的值和不需要重新渲染的组件。未来的版本可能会进行更激进的优化:

// 未来 Compiler 可能的优化:自动 code splitting
function Dashboard() {
  const [activeTab, setActiveTab] = useState('overview');

  // Compiler 可以分析出 Charts、Table、Settings 是互斥渲染的
  // 自动为它们生成 lazy 加载边界
  return (
    <Tabs value={activeTab} onChange={setActiveTab}>
      <Tab label="Overview"><Charts /></Tab>
      <Tab label="Data"><Table /></Tab>
      <Tab label="Settings"><Settings /></Tab>
    </Tabs>
  );
}

// 未来 Compiler 的潜在输出:
function Dashboard() {
  const [activeTab, setActiveTab] = useState('overview');
  // 自动注入的 lazy 加载
  const Charts = __lazy(() => import('./Charts'));
  const Table = __lazy(() => import('./Table'));
  const Settings = __lazy(() => import('./Settings'));
  // ...
}

编译时类型检查增强:Compiler 已经拥有了对 React 代码的深层理解,未来可能会扩展为一个 React 专用的类型检查/lint 工具,在编译时捕获更多的错误模式(如违反 Hooks 规则、不安全的并发访问等)。

18.5.2 Server Components 的生态扩展

Server Components 目前与 Next.js 紧密绑定,但 React 团队的长期目标是使其成为一个通用协议

// 未来可能的 RSC 协议独立化

// 任何服务端框架都可以实现 RSC 协议
// rsc-protocol.ts
interface RSCProtocol {
  // 将 Server Component 树序列化为流
  renderToRSCStream(tree: ReactElement): ReadableStream;

  // 客户端消费 RSC 流
  createFromRSCStream(stream: ReadableStream): ReactElement;

  // Server Actions 的 RPC 协议
  callServerAction(id: string, args: unknown[]): Promise<unknown>;
}

// 不同的框架可以提供自己的实现
// - Next.js App Router
// - Remix RSC
// - Waku
// - 自定义 Node.js 服务器

18.5.3 运行时的进一步精简

随着 Compiler 承担更多的优化职责,React 运行时有机会变得更小:

  1. Hooks 规则的放松:如果 Compiler 能在编译时保证 Hooks 的调用顺序,运行时就不需要维护 Hooks 链表,开发者也不再需要遵守"不能条件调用"的规则。这本质上是用编译时分析来补偿 JavaScript 缺少 Algebraic Effects 的不足。

  2. Diff 算法的优化:Compiler 可以在编译时标记哪些子树是静态的(永远不会变化),使得运行时可以跳过这些子树的 Diff。Vue 3 已经通过模板编译实现了类似的"静态提升",React Compiler 有能力在 JSX 上实现同等甚至更强的优化。

  3. 选择性引入:随着 tree-shaking 技术的成熟,未来的 React 可能会将并发渲染、Suspense、Server Components 等特性做成"按需加载"的模块——如果你的应用不使用并发特性,对应的调度器代码不会进入 bundle。

18.5.4 跨平台能力的深化

React 的 Strategy 模式(HostConfig)使其天然具备跨平台能力。未来这种能力可能进一步深化:

// 未来可能的统一跨平台 API

// 共享的业务逻辑层
function useProductSearch(query: string) {
  const [results, setResults] = useState<Product[]>([]);
  const [loading, startTransition] = useTransition();

  useEffect(() => {
    startTransition(async () => {
      const data = await searchProducts(query);
      setResults(data);
    });
  }, [query]);

  return { results, loading };
}

// Web 渲染
function WebProductList({ query }: Props) {
  const { results, loading } = useProductSearch(query);
  return <div className="product-grid">...</div>;  // DOM
}

// Native 渲染
function NativeProductList({ query }: Props) {
  const { results, loading } = useProductSearch(query);
  return <ScrollView style={styles.grid}>...</ScrollView>;  // Native View
}

// 3D 渲染(React Three Fiber)
function ThreeDProductList({ query }: Props) {
  const { results, loading } = useProductSearch(query);
  return <group>...</group>;  // Three.js Scene
}

// 终端 UI 渲染(Ink)
function TerminalProductList({ query }: Props) {
  const { results, loading } = useProductSearch(query);
  return <Box flexDirection="column">...</Box>;  // 终端字符
}

四种渲染目标共享了相同的 Hooks 逻辑和状态管理,只是"策略"(HostConfig)不同。这种架构使得 React 的价值不仅在于"做 Web 开发的框架",更在于"声明式 UI 编程的通用范式"。

18.5.5 从框架到平台

React 正在从一个"前端框架"逐步演变为一个"全栈应用平台"。这个演变的轨迹已经清晰可见:

  • React 16:UI 渲染引擎(Fiber + 虚拟 DOM)
  • React 18:并发 UI 引擎(Concurrent Mode + Suspense)
  • React 19:全栈 UI 引擎(Server Components + Actions + Compiler)
  • React 未来:全栈编译器平台(Compiler 驱动的自动优化 + RSC 协议 + 跨平台渲染)

这个方向意味着"React 开发者"的定义正在扩展——不仅是写组件的人,更是构建"从数据库到像素"完整链路的工程师。

深度洞察:React 的"下一个十年"最引人注目的趋势不是某个具体的技术特性,而是一种范式转移——从"运行时做更多事"到"编译时做更多事"。这并不意味着运行时变得不重要,而是说运行时的复杂度应该对开发者隐藏,由编译器来管理。就像高级语言隐藏了汇编指令一样,React Compiler 的终极目标是让"性能优化"这件事对开发者完全透明。当这一天到来时,我们回头看 useMemouseCallback,就像今天回头看手动管理内存一样——它们曾经是必要的,但终将成为历史。

18.6 本章小结

本章从五个维度完成了对 React 架构的全景审视:

  1. 设计模式:React 源码中的 10 个核心设计模式不是偶然出现的——Observer 驱动更新、Command 封装变更、Mediator 协调调度、Strategy 实现跨平台、Visitor 遍历 Fiber 树、Factory 创建节点、Composite 组织层级、Iterator 遍历 Hooks、Memento 保存快照、Proxy 延迟加载。它们共同构成了 React 运行时的"设计骨架"。

  2. API 设计哲学:从 Class 到 Hooks 到 Compiler 的三次演进,方向始终一致——减少附带复杂度,让开发者专注于业务逻辑。每一次演进都是在"表达力"和"简单性"之间寻找更优的平衡点。

  3. 技术考古:Algebraic Effects 启发了 Hooks 但因语言限制无法直接实现,Prepack 探索了编译时执行但因复杂度被放弃,React Fire/Flare 推动了事件系统改进但未独立发布。这些"失败"的探索深刻影响了 React 最终选择的方向。

  4. 框架对比:React(Pull + VDOM)、Vue(Push + Proxy)、Svelte(编译时推送)、Solid(细粒度 Signal)代表了四种不同的架构哲学。没有最好的框架,只有最适合特定约束条件的选择。

  5. 未来展望:React 正从运行时框架向编译器平台演进。Compiler 的全面成熟、RSC 的生态扩展、运行时的精简、跨平台能力的深化,共同指向一个方向——让 React 从"你需要学习的框架"变成"你几乎不需要意识到它存在的基础设施"。

作为全专栏的收官章节,我希望你带走的不仅是技术细节,更是一种思维方式:当你面对技术选型和架构决策时,不要只问"哪个更好",要问"在当前的约束条件下,哪种取舍最合理"。React 团队十年来的每一个重大决策——选择虚拟 DOM 而非细粒度响应式、选择 Hooks 而非 Algebraic Effects、选择 Compiler 而非手动优化——都不是因为它们是"最好的"方案,而是因为它们是在给定约束下"最合理的"方案。这种务实的工程思维,才是 React 给我们最宝贵的遗产。


思考题

  1. React 的 Reconciler 使用 Strategy 模式实现了宿主环境的可插拔性,但 Scheduler 目前是 Reconciler 内建的。 如果要将 Scheduler 也设计为可插拔的(例如允许 React Native 使用不同于 Web 的调度策略),你会如何设计 SchedulerConfig 接口?这种设计会带来哪些好处和风险?

  2. Algebraic Effects 的"可恢复性"是 Hooks 无法完美模拟的核心能力。 假设未来 TC39 引入了类似 perform/handle 的语法到 JavaScript 标准中,React 的 Hooks 系统需要做哪些根本性的改变?"Hooks 规则"是否可以被完全取消?请从 Fiber 和 Hooks 链表的实现角度分析。

  3. 在 React vs Vue vs Svelte vs Solid 的架构对比中,我们提到 React 选择了"Pull 模型"。 但 React Compiler 的自动记忆化实际上引入了一种"编译时依赖追踪"——这是否意味着 React 正在向 Push 模型靠拢?如果是,React 最终会变成一个使用 Signal 的框架吗?如果不是,React 的 Pull 模型有什么不可替代的优势?

  4. Prepack 被放弃的核心原因之一是"React 组件依赖运行时才能确定的值"。 但 Server Components 改变了这一前提——Server Component 在服务端执行时,所有的 props 和数据都是已知的。这是否意味着 Prepack 的部分求值思想可以在 Server Components 的编译管线中"复活"?设计一个概念验证方案。