当前位置: 首页 > news >正文

WHAT - React Compiler Directives 让手动优化变成过去式

文章目录

  • 一、React Compiler 是什么
  • 二、什么是 React Compiler 指令
  • 三、可用的指令类型
  • 四、配合 compilationMode 一起使用
  • 五、什么情况下使用这些指令
    • 你应该使用 "use memo" 的情况
    • 你应该使用 "use no memo" 的情况
  • 六、实际项目中如何使用
    • 示例背景
    • 没有使用 Compiler 或手动优化的情况
    • 使用 React Compiler + "use memo"
    • 如果我们加上 "use no memo"
    • 编译后(概念理解)
    • 总结
  • 七、为什么可以让手动优化变成过去式
    • React.memo 做了什么
      • 优缺点
      • 缺点示例 1:手动添加(维护麻烦)
      • 示例 2:默认只做浅比较(引用类型陷阱)
      • 示例 3:依赖关系容易出错(useCallback)
      • 示例 4:闭包捕获旧状态(stale closure)
    • React Compiler 做了什么不同的事
    • 具体示例对比
      • 使用 React.memo
      • 使用 React Compiler(或 "use memo")
    • Compiler 的优势总结
    • React 团队的目标(核心理念)
    • 何时仍然使用 React.memo

一、React Compiler 是什么

https://react.dev/learn/react-compiler

React Compiler 是 React 团队推出的新编译器,用于自动优化组件性能,减少手动使用 React.memouseMemouseCallback 等操作。
它会在编译阶段分析你的组件和 Hooks,自动帮你做记忆化(memoization)优化

而要让编译器知道哪些函数要优化、哪些不要,就用到了 —— “编译指令(compiler directives)”


二、什么是 React Compiler 指令

React Compiler 指令是放在 模块(文件)顶部函数内部顶部字符串字面量

它告诉编译器:这个函数(或文件内所有函数)是否要被编译优化。

例如:

function MyComponent() {"use memo"; // 告诉编译器要优化这个组件return <div>Hello</div>;
}function LegacyComponent() {"use no memo"; // 告诉编译器不要优化这个组件return <div>旧组件</div>;
}

也可以放在文件最上方,让整个文件都生效:

"use memo"; // 本文件所有函数都启用优化export function Button() {return <button>Click</button>;
}

三、可用的指令类型

指令含义用途
"use memo"启用编译优化明确告诉编译器:这个函数(组件或 Hook)需要进行记忆化优化
"use no memo"禁用编译优化排除某些不兼容或不需要优化的函数(例如外部库逻辑或动态行为)

四、配合 compilationMode 一起使用

React Compiler 在配置文件中可以指定一个 compilationMode(编译模式)来决定默认行为

模式说明是否需要指令
'infer'(默认)编译器自动推断哪些函数应被优化(比如以大写字母开头的组件、use 开头的 Hook)不一定需要
'annotation'只有带有 "use memo" 指令的函数才会被优化推荐初期采用
'all'所有顶层函数都会尝试被优化可用 "use no memo" 排除

举例:你在 vite.config.ts 或 Babel 配置中这样设置

{reactCompiler: {compilationMode: 'annotation'}
}

然后在组件里写:

function UserCard() {"use memo"; // 必须显式启用return <div>User</div>;
}

其实就是通过 React Compiler Directive 减轻选择哪个优化 API(useMemo、useCallback) 的决策负担。


五、什么情况下使用这些指令

React Compiler 的目标是让手动优化变成过去式,但在使用这个能力的过渡期内,你还是需要明确告诉它哪些代码可以安全优化。

你应该使用 “use memo” 的情况

  • 当前项目处于 'annotation' 模式;
  • 组件性能关键,希望编译器参与优化;
  • 组件遵守 React 规范(无副作用、Hooks 规则正确等)。

你应该使用 “use no memo” 的情况

  • 某组件/函数中使用了不兼容的模式(例如动态调用 Hook);
  • 使用外部库或遗留逻辑,编译器优化可能出问题;
  • 暂时想排除某部分代码进行调试。

六、实际项目中如何使用

我们现在来通过一个具体、可运行的例子,演示 React Compiler 的指令("use memo" / "use no memo") 是如何影响组件优化行为的。

示例背景

假设我们有一个页面,它渲染了一个父组件 App 和一个子组件 UserCard
父组件会频繁更新(比如计数器变化),但 UserCardprops 实际上没有变化


没有使用 Compiler 或手动优化的情况

// App.tsx
import { useState } from "react";function UserCard({ name }: { name: string }) {console.log("UserCard 渲染");return <div>{name}</div>;
}export default function App() {const [count, setCount] = useState(0);console.log("App 渲染");return (<div><button onClick={() => setCount(c => c + 1)}>点击 +1</button><p>Count: {count}</p><UserCard name="Alice" /></div>);
}

每次点击按钮时,App 会重新渲染,同时 UserCard 也会跟着渲染,即使它的 props 没变。

控制台输出会是:

App 渲染
UserCard 渲染
App 渲染
UserCard 渲染
...

使用 React Compiler + “use memo”

假设你在项目中启用了 React Compiler(React 19+ / 或通过 Babel 插件)
并设置编译模式为 "annotation"

// vite.config.ts
export default defineConfig({reactCompiler: {compilationMode: "annotation",},
});

然后我们修改组件如下:

// App.tsx
import { useState } from "react";function UserCard({ name }: { name: string }) {"use memo"; // 告诉 React Compiler 对这个组件启用自动记忆化优化console.log("UserCard 渲染");return <div>{name}</div>;
}export default function App() {"use memo"; // 可选,App 组件也能被优化const [count, setCount] = useState(0);console.log("App 渲染");return (<div><button onClick={() => setCount(c => c + 1)}>点击 +1</button><p>Count: {count}</p><UserCard name="Alice" /></div>);
}

此时,React Compiler 会在编译阶段:

  • 分析 UserCard 的 props;
  • 自动生成与 React.memo(UserCard) 等价的优化;
  • name 没变化时,跳过子组件重新渲染。

点击按钮多次后,你会看到:

App 渲染
UserCard 渲染   // 首次渲染
App 渲染           // 后续点击
App 渲染
...

UserCard 不再重复渲染!这就是 React Compiler 自动 memo 化 的效果。


如果我们加上 “use no memo”

function UserCard({ name }: { name: string }) {"use no memo"; // 显式禁用优化console.log("UserCard 渲染");return <div>{name}</div>;
}

输出又回到了:

App 渲染
UserCard 渲染
App 渲染
UserCard 渲染
...

也就是说:

  • "use memo" → 自动跳过重复渲染(类似 React.memo)
  • "use no memo" → 禁止优化(总是重新渲染)

编译后(概念理解)

React Compiler 在构建阶段(非运行时)会把:

function UserCard({ name }) {"use memo";return <div>{name}</div>;
}

变成类似:

const UserCard = React.memo(function UserCard({ name }) {return <div>{name}</div>;
}, shallowCompareProps);

同时还会为内部状态、闭包引用生成优化逻辑,使得 React 在运行时不必重复执行相同渲染。


总结

情况指令编译器行为渲染效果
默认(无指令)取决于 compilationMode 推断通常会重渲染
"use memo"显式启用优化自动记忆化(等价于 React.memo)不重复渲染
"use no memo"显式禁用优化跳过编译优化每次都渲染

七、为什么可以让手动优化变成过去式

「既然 React.memo 就能避免重复渲染,
为什么还要搞一个 "use memo" 或新的 Compiler 呢?」

让我们一步步拆解。


React.memo 做了什么

优缺点

React.memo运行时(runtime) 的优化工具。

我们手动告诉 React:

“如果 props 没变,就别重渲染这个组件。”

示例:

const UserCard = React.memo(function UserCard({ name }) {console.log("渲染 UserCard");return <div>{name}</div>;
});

如果父组件更新、但 name 没变,React 会跳过这个组件的重新渲染。

优点:

  • 简单直接,兼容性好;
  • 可控:你决定哪些组件 memo 化。

缺点:

  • 你得手动添加
  • 默认只做浅比较;
  • 需要你理解依赖关系;
  • 容易出错(如闭包捕获错误状态、依赖项没更新等)。

缺点示例 1:手动添加(维护麻烦)

很多人以为“只要用上 React.memo 就万事大吉”,其实不然。

// ❌ 示例:你得手动添加 React.memo,否则子组件每次都重渲染function Child({ value }: { value: number }) {console.log("👶 Child 渲染");return <div>{value}</div>;
}export default function Parent() {const [count, setCount] = useState(0);const [text, setText] = useState("");return (<><Child value={count} /><input value={text} onChange={e => setText(e.target.value)} /><button onClick={() => setCount(count + 1)}>+1</button></>);
}

即使你只是输入文字,Child 也会重新渲染。


修正:手动加上 React.memo

const Child = React.memo(function Child({ value }: { value: number }) {console.log("👶 Child 渲染");return <div>{value}</div>;
});

这次 text 改变不会触发 Child 重渲染。但问题是——得手动写 memo,多个子组件就得一一添加,很繁琐。

而 React Compiler 未来会自动帮你做这些分析。


示例 2:默认只做浅比较(引用类型陷阱)

const Child = React.memo(function Child({ user }: { user: { name: string } }) {console.log("Child 渲染");return <div>{user.name}</div>;
});export default function Parent() {const [count, setCount] = useState(0);const user = { name: "Tom" };return (<><Child user={user} /><button onClick={() => setCount(count + 1)}>+1</button></>);
}

即使 user.name 没变,Child 仍然每次渲染。

因为 user 每次都是一个新对象:{ name: "Tom" } !== { name: "Tom" }


修正:手动 memo 化引用

const user = useMemo(() => ({ name: "Tom" }), []);

但这样又会出现:

  • 需要你知道何时 memo;
  • 多个 props 时依赖管理复杂。

在未来,这类“浅比较导致的无效渲染”也是 React Compiler 自动分析能解决的。


示例 3:依赖关系容易出错(useCallback)

function Counter() {const [count, setCount] = useState(0);// ❌ 忘了写依赖 countconst handleClick = useCallback(() => {setCount(count + 1);}, []); return <button onClick={handleClick}>+1</button>;
}

这段代码点击几次后会发现:count 永远只到 1。

因为回调函数被缓存住了,它内部引用的 count 永远是初始的 0。


正确写法

const handleClick = useCallback(() => {setCount(count + 1);
}, [count]);

但这样会使 handleClick 每次都变,传给子组件又会导致重复渲染。

所以开发者常常陷入两难:

不加依赖会错,加了依赖又没性能提升。

在未来,React Compiler 会自动分析哪些依赖需要追踪,不再靠人工判断。


示例 4:闭包捕获旧状态(stale closure)

function Example() {const [count, setCount] = useState(0);const logLater = useCallback(() => {setTimeout(() => {console.log("count:", count);}, 1000);}, []); // ❌ 依赖遗漏return (<><button onClick={() => setCount(c => c + 1)}>+1</button><button onClick={logLater}>延迟打印</button></>);
}

操作:

  1. 点击 +1 若干次;
  2. 点击“延迟打印”;
  3. 一秒后打印结果却是旧的 count。

原因:

  • setTimeout 的回调捕获了旧的 count
  • 因为 useCallback 依赖数组是 []
  • 所以永远不会更新。

正确写法

const logLater = useCallback(() => {setTimeout(() => {console.log("count:", count);}, 1000);
}, [count]);

但同样——这会频繁创建新函数,手动维护依赖太脆弱。

通过上述示例,可以发现开发者经常面临着依赖管理和性能的决策成本。

React Compiler 做了什么不同的事

React Compiler 是一个 编译阶段的优化器,它在 构建时 分析你的组件和 hooks。

目标:让你不再需要手动用 React.memouseMemouseCallback

举个直观对比:

行为React.memoReact Compiler
优化时机运行时(runtime)编译时(build time)
触发方式手动包裹组件自动分析
记忆化逻辑浅比较 props语义级依赖分析(更智能)
性能开销每次渲染都需比较 props编译后生成更高效代码,无运行时开销
易用性需要你手写 memo自动化,几乎“零心智负担”

具体示例对比

使用 React.memo

const UserCard = React.memo(function UserCard({ name, age }) {console.log("UserCard 渲染");return <div>{name} - {age}</div>;
});

React 在运行时每次都要比较 props.nameprops.age。如果引用没变,就跳过渲染。但如果你传入了匿名函数、对象、数组(比如 onClick={() => ...}),它仍然会触发重渲染,因为浅比较失败。


使用 React Compiler(或 “use memo”)

function UserCard({ name, age }: { name: string; age: number }) {"use memo";console.log("UserCard 渲染");return <div>{name} - {age}</div>;
}

React Compiler 在构建阶段分析依赖关系:

  • 如果 UserCard 的 props 未变化,它直接跳过渲染;
  • 它还能理解闭包中的依赖,比如:
function App() {"use memo";const [count, setCount] = useState(0);const handleClick = () => setCount(c => c + 1); // ✅ Compiler 能自动 memo 化这个函数
}

Compiler 会生成等价于手动使用 useCallback 的优化代码。无需你写 useCallback


Compiler 的优势总结

对比项React.memo / useMemo / useCallbackReact Compiler
写法需要显式调用 API自动处理(或用 "use memo" 指令)
优化范围仅限组件 props、单个函数全局依赖分析(包括 Hooks、闭包变量)
运行时成本有浅比较、内存开销编译阶段生成优化代码,无额外运行时成本
易错点忘记写依赖、依赖数组错误编译器静态检查并自动生成
迁移成本手动优化零侵入,渐进启用

React 团队的目标(核心理念)

React Compiler 的核心理念是:

开发者不该再手动管理 memo 化逻辑
让编译器在构建阶段自动分析组件依赖和渲染路径。」

也就是说:

  • 你写的只是“纯粹的组件逻辑”;
  • React Compiler 自动生成最优执行路径。

未来,React.memouseMemouseCallback 都会成为“legacy escape hatch”(遗留逃生舱),除非有特殊情况。


何时仍然使用 React.memo

虽然 React Compiler 会取代大部分优化手段,但目前仍有一些情况你可能暂时需要手动用 React.memo

  • 你的项目还没升级到 React 19 / 未启用 Compiler;
  • 依赖的第三方库未兼容 Compiler;
  • 某个组件使用了编译器尚不支持的模式;
  • 性能瓶颈明确且需要立即解决。

http://www.dtcms.com/a/535771.html

相关文章:

  • API请求关键指标全解:Apipost视角下,从连接到性能的全景分析
  • HTML 的底层原理
  • 布吉做网站的公司关于网站建设的图片
  • 服务器运维(六)网站访问分析统计——东方仙化神期
  • 【Docker】定制化构建一个可以运行GaussDB的kylinv10sp3系统的docker镜像
  • 5分钟搭建云IDE!CodeServer+cpolar打造跨设备开发工作站
  • vmware和kali linux安装和搭建
  • 网络:网络层(IP协议)和数据链路层
  • DDS和SOMEIP区别以及应用场景
  • Lumi 具神智能机器人 SDK说明和ACT算法中的学习与推理
  • Rokid AR眼镜开发入门:构建智能演讲提词器Android应用
  • 量化指标解码03:布林带的开口收口策略与市场波动性分析
  • 深圳网站建设报价表廊坊建手机网站
  • 余姚网站推广wordpress多个域名
  • CSS引入方式(本文为个人学习笔记,内容整理自哔哩哔哩UP主【非学者勿扰】的公开课程。 > 所有知识点归属原作者,仅作非商业用途分享)
  • SuperMap iObjects .NET 11i 二次开发(十六)—— 叠加分析之合并
  • 【Linux笔记】网络部分——传输层协议UDP
  • Ansible 自动化项目结构与 Harbor 安装示例(基于 kubeasz)①
  • Spring 源码学习(十五)—— HandlerMethodReturnValueHandler
  • everviz 数据可视化平台
  • 12 U盘挂载
  • 【Kylin Linux root 密码故障处置指南(超限重试 + 改回原密码)】
  • 网络原理:数据链路层、NAT与网页加载
  • 【从零开始开发远程桌面连接控制工具】01-项目概述与架构设计
  • 网站建设竞价托管什么意思在国内做推广产品用什么网站好
  • 有没有做宠物的网站网站开发中间商怎么做
  • 深度强化学习 | 详解从信赖域策略优化(TRPO)到近端策略优化(PPO)算法原理
  • 在类中定义装饰器:Python高级元编程技术详解
  • [C++][正则表达式]常用C++正则表达式用法
  • 基于大数据的短视频数据分析系统 Spark哔哩哔哩视频数据分析可视化系统 Hadoop大数据技术 情感分析 舆情分析 爬虫 推荐系统 协同过滤推荐算法 ✅