React 编译器:停止手动优化你的 React 应用

发布: (2026年2月4日 GMT+8 05:47)
10 min read
原文: Dev.to

Source: Dev.to

请提供您希望翻译的正文内容,我将按照要求保留原始链接、格式和代码块,仅翻译文本部分。

在我们的团队 KATA 会议期间,一位同事提出了一个我敢打赌你也想过的问题:

“如果 React 已经知道只渲染已改变的元素,为什么我们还需要手动进行任何优化?”

这是一个精彩的问题。答案揭示了我们多年来一直忍受的一个主要痛点——也展示了 React 编译器是如何在几个关键领域提供帮助的。

下面是一段关于 React 优化演进的旅程,用一个简单的类比来说明:餐厅厨房

🍝 餐厅厨房:React 实际 的工作方式

想象你的应用就是一个厨房。

角色类比
主厨(父组件)管理整个厨房。
线厨(子组件)负责具体的工作站。

在标准的 React 应用中,每当主厨改变了什么——哪怕只是补充盐——他们都会敲响一只巨大的铃。所有的厨师都会停下来重新做他们的工作,即使他们的工作站没有任何变化。

React 的默认行为: 当父组件重新渲染时,所有子组件都会重新渲染。

多年来,我们不得不编写额外的代码(hooks)来向 React 的优化引擎下达指令。下面看看单个组件是如何演变的:

  1. 没有 hooks(没有向编译器提供指令)
  2. 使用 hooks(向 React 的优化技术提供指令)
  3. React 编译器代码(自动进行优化)

Source:

组件的演进

我们将使用一个 RestaurantMenu 组件,它:

  • 保存菜品列表。
  • 对其进行过滤(一次昂贵的计算)。
  • 渲染一个子组件列表(子组件)。

第 1 阶段:代码(干净但慢)

这是大多数初学者写的代码。它看起来整洁,却隐藏了性能陷阱。

import { useState } from 'react';

// 一个简单的子组件
const DishList = ({ dishes, onOrder }) => {
  console.log('🍝 Rendering DishList (Child)'); // {/* items... */};
};

export default function RestaurantMenu({ allDishes, theme }) {
  const [category, setCategory] = useState('pasta');

  // ⚠️ 问题 1:昂贵的计算在每次渲染时都会运行
  const filteredDishes = allDishes.filter(dish => {
    console.log('🧮 Filtering... (Slow Math)');
    return dish.category === category;
  });

  const handleOrder = dish => {
    console.log('Ordered:', dish);
  };

  return (
    <>
      {/* 点击此按钮会导致重新渲染 */}
      <button onClick={() => setCategory('salad')}>Switch Category</button>

      {/* ⚠️ 问题 2:内联箭头函数 */}
      {/* (dish) => handleOrder(dish) 在每次渲染时都会创建一个全新的函数,
          强制 DishList 重新渲染。 */}
      <DishList dishes={filteredDishes} onOrder={dish => handleOrder(dish)} />
    </>
  );
}

控制台会输出什么?

即使是一次轻微的父组件重新渲染(例如点击按钮),也会触发:

🧮 Filtering... (Slow Math)
🍝 Rendering DishList (Child)

每一次交互都会记录这两条信息——浪费!

第 2 阶段:使用 Hook 的解决方案(附加说明)

为了解决这个问题,我们传统上引入 hooksuseMemouseCallbackmemo

import { useState, useMemo, useCallback, memo } from 'react';

// 方案 A:使用 memo 包裹子组件,防止无意义的重新渲染
const DishList = memo(({ dishes, onOrder }) => {
  console.log('🍝 Rendering DishList (Child)');
  return /* items... */;
});

export default function RestaurantMenu({ allDishes, theme }) {
  const [category, setCategory] = useState('pasta');

  // 方案 B:使用 useMemo 缓存计算
  const filteredDishes = useMemo(() => {
    console.log('🧮 Filtering... (Slow Math)');
    return allDishes.filter(dish => dish.category === category);
  }, [allDishes, category]);

  // 方案 C:使用 useCallback 冻结函数
  const handleOrder = useCallback(dish => {
    console.log('Ordered:', dish);
  }, []);

  return (
    <>
      <button onClick={() => setCategory('salad')}>Switch Category</button>

      {/* ⚠️ 陷阱:我们**不能**在这里使用内联箭头函数!
          如果写成: onOrder={(dish) => handleOrder(dish)}
          将会破坏优化,因为包装器会创建一个新的引用。
          必须直接传入稳定的函数。 */}
      <DishList dishes={filteredDishes} onOrder={handleOrder} />
    </>
  );
}

现在控制台会输出什么?

如果父组件因不影响 filteredDisheshandleOrder 的原因(例如 theme 改变)而重新渲染,不会有任何日志

(静默。没有日志出现。)

性能得到提升,但代码因额外的 Hook 样板而变得更难阅读。

注意: 如果同事把 onOrder={handleOrder} 改成 onOrder={() => handleOrder()},优化会悄然失效——箭头函数每次渲染都会创建新函数。

第 3 阶段:React 编译器的解决方案(无需额外代码)

引入 React 编译器(例如 React 18 的自动记忆化)。它可以在不显式使用 Hook 的情况下推断出相同的优化。

import { useState } from 'react';

// 不使用 useMemo、useCallback、memo。
export default function RestaurantMenu({ allDishes, theme }) {
  const [category, setCategory] = useState('pasta');

  // 编译器会自动对这段昂贵的计算进行记忆化。
  const filteredDishes = allDishes.filter(dish => {
    console.log('🧮 Filtering... (Slow Math)');
    return dish.category === category;
  });

  // 编译器

自动稳定此函数引用。

const handleOrder = dish => {
  console.log('Ordered:', dish);
};

return (
  <>
    <button onClick={() => setCategory('salad')}>Switch Category</button>

    {/* The child component can stay a plain function component. */}
    <DishList dishes={filteredDishes} onOrder={handleOrder} />
  </>
);
}

// Plain child component – no need for React.memo.
const DishList = ({ dishes, onOrder }) => {
  console.log('🍝 Rendering DishList (Child)');
  return /* items... */;
};

现在会发生什么?

  • filter 仅在 allDishescategory 实际变化时运行。
  • handleOrder 函数在渲染之间保持稳定的引用。
  • DishList 仅在其属性真正变化时重新渲染。

所有这些都在 不使用 手动的 useMemouseCallbackmemo 样板代码的情况下实现。

TL;DR

Phase你写的代码你得到的结果
1 – 普通代码简单、可读的代码不必要的重新渲染 & 昂贵的工作
2 – Hook‑密集useMemouseCallbackmemo已优化但代码嘈杂且易出错
3 – 编译器再次使用普通代码相同(或更好)的性能 自动 实现

React 编译器让你在保持 Phase 1清晰度 的同时,获得 Phase 2性能。不再有“魔法”内联箭头 bug,也不再需要手动记忆化——只需按你对 UI 的思考方式编写代码,交给编译器完成繁重工作。

React 编译器魔法 – 餐厅类比

const handleOrder = (dish) => {
  console.log("Ordered:", dish);
};

return (
  <>
    <button onClick={() => setCategory('salad')}>Switch Category</button>

    {/* ✅ COMPILER MAGIC: We can use an inline arrow again!
        The compiler is smart enough to "memoize" this arrow function
        wrapper automatically. It sees that `handleOrder` is stable, 
        so it makes this arrow stable too. */}
    <DishList dishes={filteredDishes} onOrder={dish => handleOrder(dish)} />
  </>
);

控制台会发生什么?

即使我们删除了所有 hook,结果仍然与 Phase 2 完全相同。

🖥️ CONSOLE OUTPUT:
---------------------------------------------
(Silent. No logs appear.)

到底发生了什么?

React 编译器在 构建时 分析了你的代码。它对数据流的理解甚至超过了我们。

  • 它看到 filteredDishes 只会在 category 改变时变化。
  • 它看到你把 handleOrder 包装在箭头函数 (dish) => handleOrder(dish) 中。
  • 它自动缓存这个箭头函数包装,使其在每次渲染中保持完全相同的引用。
  • 它实际上在幕后为你生成了 Phase 2 的优化代码。

思想转变

多年来,我们必须手动告诉框架:“记住这个变量!冻结这个函数!”

React 编译器解决了这个问题!

React 现在承担了优化的负担。它让我们不再担心渲染周期和依赖数组,而可以专注于真正重要的事情:交付功能

What Now?

The best part is that React Compiler is backward compatible (React v17, v18 as well). You don’t have to rewrite your codebase. Just enable it, and it will optimize your “plain” components while leaving your existing hooks untouched.


Thanks for reading! This is my first post on Dev.to, and I wrote it to help solidify my own understanding of the Compiler. I’d love your feedback—did the restaurant analogy make sense to you? Let me know in the comments!

Back to Blog

相关文章

阅读更多 »