跪拜 Guibai
← Back to the summary

Vue 3.6 Vapor Mode and React 19 Compiler Are Two Opposite Bets on Killing Wasted Re-renders

Welcome to follow the WeChat public account: FSA Full Stack Action 👋

I. Background: The "Tax" of Re-rendering

When talking about frontend performance optimization, the most noteworthy development this year is that two mature ecosystems have taken completely opposite paths in the same "battle."

Facing the same core pain point—"Wasted Re-renders"—these two vendors have given completely different answers. Vue 3.6 launched Vapor Mode, which attempts to completely bypass the Virtual DOM and directly compile components into extremely precise DOM operations; while React 19's stable compiler release chose to preserve the Virtual DOM's status but uses compilation to let developers completely say goodbye to manual optimization.

If you are responsible for your frontend team's technology selection this year, you must figure out what logic is at play under the hood of these two solutions. Because the trade-offs of these two paths are real, and their impact will far exceed the current framework hype.

In fact, the most performance-intensive thing is never the DOM operation itself. In React's default top-down "Pull" model, once a component's state updates, that component and all its child components will re-execute. Although the Virtual DOM finds differences through the diff algorithm and performs patch operations, the cost is: to discover "no change," you must re-run all the JavaScript logic inside the component—those filter, map, and complex calculation logics.

This is nothing on a static page, but in a high-frequency updating stock trading dashboard, or an e-commerce application with extremely complex logic, this continuous re-evaluation will directly occupy the main thread.

Over the past few years, the industry's solution has usually been "manual memoization": we were forced to sprinkle useMemo, useCallback, and React.memo everywhere in our code, and then spend a lot of time troubleshooting missing dependency arrays. I call this the "re-rendering tax." By 2024, most large React projects are paying expensive development and performance costs for this "tax."

II. Two Technical Routes Converging on the Same Goal

To "abolish" this tax, the industry has split into two camps: one is the "fine-grained reactive (Signals)" camp represented by SolidJS, Vue, and Angular; the other is React, which sticks to the original model but decides to shift the problem to compile time.

1. The Signals Camp: The Ultimate in Fine-Grained Updates

Simply put, a Signal is a reactive value that knows "who depends on it."

In the logic of SolidJS or Vue, when you put a Signal into a template, the framework directly registers that specific DOM node as a subscriber. When the value changes, there is no need to re-execute the entire component function; the Signal will directly and precisely find that text node and update it.

import { createSignal } from "solid-js";

function Counter() {
  const [count, setCount] = createSignal(0);
  // This line runs only once when the component is created
  console.log("Component mounted.");
  return (
    <button onClick={() => setCount(c => c + 1)}>
      Count: {count()} {/* Only this text node will update */}
    </button>
  );
}

If you click the button a hundred times, you will find that console.log never triggers again. The component function is no longer a "render function" that re-runs every time, but a "setup function" that runs only once, responsible for drawing the dependency graph.

There is a technical detail here that is easily overlooked: modern Signal implementations (like Solid, Vue, Angular) are not purely "Push" mode, but a "Push-Pull" hybrid mechanism. When the source changes, it pushes a "Dirty" flag to the dependency graph, but the specific calculation logic remains "Lazy," only recalculating when the value actually needs to be read. This mechanism effectively avoids redundant intermediate calculations, preventing the logic "Glitches" common in those Observable libraries of the 2010s.

2. React Compiler: Leaving Optimization to the Build Phase

React's thinking is completely different. They did not intend to abandon the mental model of the Virtual DOM, but instead completed automatic optimization at the compilation stage through the React Compiler.

React Compiler is not a runtime library, but a compile-time analyzer. It analyzes your components through the AST (Abstract Syntax Tree), figures out which values depend on which inputs, and then automatically writes those useMemo calls you used to write manually.

For example, this ordinary code you write:

function ProductList({ products, filterText }) {
  const filteredProducts = products.filter(p => p.name.includes(filterText));
  return (
    <ul>
      {filteredProducts.map(product => (
        <li key={product.id}>{product.name}</li>
      ))}
    </ul>
  );
}

After compiler processing, the underlying logic actually becomes like this (simplified version):

function ProductList(t0) {
  const $ = _c(7); // Cache slot
  const { products, filterText } = t0;
  let filteredProducts;
  // Only re-execute filter when products or filterText change
  if ($[0] !== products || $[1] !== filterText) {
    filteredProducts = products.filter(p => p.name.includes(filterText));
    $[0] = products;
    $[1] = filterText;
    $[2] = filteredProducts;
  } else {
    filteredProducts = $[2];
  }
  // ... subsequent JSX caching logic
}

The code you write remains clean, but performance has been improved. However, note that there is a prerequisite: your code must strictly follow the Rules of React. If your logic is too messy (like randomly changing setState inside useEffect), the compiler will directly "strike" and give up optimizing that component.

III. Why Did React Reject Signals?

Since Signals are so fast, why didn't the React team just follow suit? This is actually not due to stubbornness, but a deliberate architectural choice.

React insists that UI must be a "pure snapshot" of state at a certain moment, and flow in a single direction. The essence of Signal is to decouple state from the component lifecycle, turning values into mutable references that can be subscribed to anywhere. While this feature speeds up rendering, it also brings another potential risk: Reactive Spaghetti. In large projects, when data flow becomes hard to track, maintenance costs will surge.

Furthermore, there is a deeper conflict: React's "Concurrent Rendering" capability. React can pause, prioritize, and even restart a background task. A Signal that directly writes to the DOM intervening mid-render could cause a "Tearing" phenomenon—where different parts of the screen display different versions of the same state.

So, React's choice is: Don't change your mental model, but change your build process.

IV. Finally: How to Make a Decision?

Facing these two paths, there is no so-called "optimal solution," only "trade-offs" based on project constraints. I've compiled a comparison table, hoping to help you clarify your thoughts:

Dimension

Signals Camp (Vue/Solid/Angular)

React Compiler Camp

Core Philosophy

Fine-grained subscription based on Signals

Automatic compilation based on Virtual DOM

Main Advantages

Extremely precise rendering, almost no redundant JS execution

Zero migration cost, maintains a highly declarative mental model

Potential Challenges

Data flow tracking can become complex

Strict requirements on code standards, must follow Rules of React

Applicable Scenarios

High-frequency updates (real-time dashboards, maps, collaboration tools)

Large legacy projects, teams with high requirements for developer experience

In short, the "manual optimization era" of frontend is coming to an end. Whether it's Vue's pursuit of the ultimate performance of "no Virtual DOM" or React's pursuit of the ultimate experience of "automatic memoization," the core is shifting towards compile time.

Finally, I want to ask everyone: After enabling React Compiler, did you really delete those useMemo and useCallback calls? Or do they still remain in your codebase as some kind of "security blanket"? Welcome to share your real feelings in the comments.

If the article was helpful to you, please don't hesitate to click and follow my WeChat public account: FSA Full Stack Action, this will be the greatest encouragement for me. The public account not only has Android technology, but also iOS, Python and other articles, there may be skill knowledge points you want to know oh~