大家好,今天我们来深入探讨 React 内部一项至关重要的性能优化技术——常量提升(Constant Hoisting)。这项技术在幕后默默工作,由编译器而非 React 运行时实现,它针对的是我们日常编写的静态 JSX 节点,通过巧妙的转换,极大地提升了应用性能和渲染效率。
我们将从 React 渲染的基本原理出发,逐步揭示静态 JSX 节点重复创建的性能瓶颈,进而引出常量提升的解决方案。随后,我们将详细剖析编译器(特别是 Babel 插件)是如何识别、转换这些静态节点,以及这种优化如何与 React 的协调(Reconciliation)算法协同,带来显著的性能收益。
一、React 渲染的基础:JSX 与虚拟 DOM 的开销
React 的核心思想是让开发者以声明式的方式描述 UI,而 JSX 语法就是这种描述的直观体现。当我们编写 JSX 时,它实际上被 Babel 等编译器转换成了 React.createElement 函数的调用。
例如,以下 JSX 代码:
function MyComponent() {
return (
<div className="container">
<h1>Hello, React!</h1>
<p>Welcome to our deep dive.</p>
</div>
);
}
在编译后(使用旧版 JSX 转换,即 React.createElement),大致会变成这样:
function MyComponent() {
return React.createElement(
"div",
{ className: "container" },
React.createElement("h1", null, "Hello, React!"),
React.createElement("p", null, "Welcome to our deep dive.")
);
}
或者,如果使用新版 JSX 转换(React 17+ 默认,即 _jsx 或 _jsxs),则会是:
import { jsx as _jsx } from "react/jsx-runtime";
import { jsxs as _jsxs } from "react/jsx-runtime";
function MyComponent() {
return _jsxs("div", {
className: "container",
children: [
_jsx("h1", { children: "Hello, React!" }),
_jsx("p", { children: "Welcome to our deep dive." }),
],
});
}
无论是 React.createElement 还是 _jsx/_jsxs,它们都在做一件事情:在每次组件渲染时,创建一系列描述 UI 结构和属性的 JavaScript 对象。这些对象构成了我们常说的“虚拟 DOM 树”。
当组件的状态或 props 发生变化,导致组件重新渲染时:
- 重复创建虚拟 DOM 对象: React 会再次执行组件函数,生成一个新的虚拟 DOM 树。这意味着大量的 JavaScript 对象需要被分配到内存中。
- 协调算法开销: React 接着会将新的虚拟 DOM 树与上一次渲染的虚拟 DOM 树进行比较(即协调 Reconciliation 过程),找出两者之间的差异。这个比较过程虽然高效,但仍然有其固有的计算成本。
- 潜在的 DOM 更新: 如果发现差异,React 才会对实际的浏览器 DOM 进行必要的更新。
对于那些包含动态内容(例如,基于 state 或 props 变化的文本、属性或子元素)的 JSX 节点,这种重复创建和比较是不可避免的,也是 React 声明式 UI 的核心机制。然而,对于那些完全静态的 JSX 节点——即在组件的整个生命周期中,它们的结构、属性和内容都永不改变的部分——重复地创建它们并进行比较,就成了一种不必要的性能浪费。
想象一个组件,它有一个固定的头部、一个动态的内容区和一个固定的底部。每次内容区更新时,整个组件都会重新渲染,导致头部和底部这些静态部分也会被重新创建虚拟 DOM 对象,并参与到协调算法中。这种开销在大型应用和频繁更新的场景下会变得非常显著。
二、常量提升:解决静态 JSX 冗余创建的方案
常量提升(Constant Hoisting),在 React 的上下文中,特指编译器识别出组件渲染函数中完全静态的 JSX 子树,并将其“提升”到渲染函数外部,作为只创建一次的常量。
核心思想:
如果一个 JSX 元素及其所有子元素、属性在组件的任何渲染周期中都保持不变,那么我们只需要在应用启动时(或者说,在组件模块加载时)创建一次这个虚拟 DOM 对象,并在后续的所有渲染中重复使用这个已创建的对象。
为什么叫“提升”?
“提升”这个词在这里借用了 JavaScript 中变量和函数声明提升的概念,但其含义更侧重于将运行时创建的资源“提前”到编译时或模块初始化时创建。编译器将这些静态 JSX 对应的 React.createElement(或 _jsx)调用结果,从组件函数内部移到其作用域之外,通常是模块的顶级作用域,并赋值给一个常量。
带来的好处:
- 减少
React.createElement(或_jsx) 调用: 这是最直接的收益。静态 JSX 子树的虚拟 DOM 对象只在模块加载时创建一次,而不是在每次组件渲染时都创建。 - 降低内存分配: 避免了每次渲染都为相同的静态虚拟 DOM 对象分配新的内存,减轻了垃圾回收器的压力,减少了因频繁 GC 导致的卡顿。
- 加速协调过程: 当 React 进行协调时,如果发现新旧虚拟 DOM 树中的某个节点是严格相等的(即
===比较为 true),它就可以立即判断这个节点及其整个子树都没有发生变化,从而完全跳过对这个子树的深度比较。这是一个非常高效的短路优化路径。 - 提高 V8 引擎优化潜力: 减少了函数内部的复杂操作,有助于 JavaScript 引擎更好地对代码进行 JIT 优化。
三、编译器的角色:识别与转换
这项优化并非 React 运行时本身实现,而是由构建工具链中的编译器(如 Babel)完成的。它在 JavaScript 代码被打包和运行之前,对 JSX 语法进行静态分析和转换。
3.1 关键的 Babel 插件
在 React 生态中,实现常量提升的主要是 Babel 插件。最直接相关的是:
@babel/plugin-transform-react-constant-elements:这个插件专门用于识别和提升静态 React 元素。@babel/plugin-transform-react-jsx:虽然它主要负责将 JSX 转换为React.createElement或_jsx调用,但现代版本的 JSX 转换(尤其是在使用react/jsx-runtime时)也为常量提升提供了更好的基础,因为它能更精确地创建和标识元素。
工作原理概述:
- 抽象语法树 (AST) 解析: Babel 首先将 JSX 和 JavaScript 代码解析成一个 AST。这个 AST 是代码的树状表示,每个节点代表代码中的一个结构(例如,函数声明、变量赋值、JSX 元素等)。
- 静态分析: 编译器遍历 AST,识别出 JSX 元素节点。对于每个 JSX 元素,它会检查其所有属性、子元素以及更深层次的子孙元素,判断它们是否完全静态。
- 判断标准: 一个 JSX 节点被认为是静态的,必须满足以下条件:
- 所有属性都是静态值: 属性值不能是变量、表达式、函数调用,或者任何可能在不同渲染中产生不同结果的值。例如,
className="foo"是静态的,而className={isActive ? "active" : "inactive"}或className={styles.myClass}(如果styles是动态导入或计算的)则不是。 - 所有子元素都是静态的: 如果子元素是文本,文本内容必须是常量。如果子元素是其他 JSX 元素,这些子元素本身也必须满足静态条件。不能包含
map循环、条件渲染(除非条件本身是常量)或其他动态生成内容的逻辑。 - 没有
ref属性:ref属性通常用于获取 DOM 节点的引用,这意味着节点可能需要被动态操作,因此不能被视为纯粹的静态。 - 没有
key属性(或key是静态字符串): 动态的key属性会阻止常量提升,因为key是 React 协调算法中用于识别元素身份的重要线索。如果key变化,元素会被视为不同的。 - 不是自定义组件实例: 通常,常量提升主要应用于原生的 HTML 元素(如
<div>,<span>,<h1>),因为自定义组件 (<MyCustomComponent />) 内部的渲染逻辑是动态的,其返回的虚拟 DOM 结构可能因 props 变化而不同。虽然自定义组件的 props 可以是静态的,但组件本身每次仍会执行。不过,如果一个自定义组件被React.memo包裹,并且它的所有 props 都是静态的,那么其 渲染结果 在某种程度上也可以被视为静态,但这不是常量提升直接处理的范围。常量提升更侧重于将createElement调用的 结果 对象提升。 - 不包含展开属性 (
{...props}): 展开属性意味着属性列表是动态的。 - 不包含 Context API 的消费者或提供者: 这些通常也带有动态行为。
- 所有属性都是静态值: 属性值不能是变量、表达式、函数调用,或者任何可能在不同渲染中产生不同结果的值。例如,
- 判断标准: 一个 JSX 节点被认为是静态的,必须满足以下条件:
- 代码转换: 一旦识别出静态 JSX 子树,编译器就会执行以下转换:
- 将该静态 JSX 子树对应的
React.createElement(或_jsx)调用移动到组件函数外部,通常是模块的顶部作用域。 - 将这些调用的结果赋值给一个新的、唯一的常量变量(例如
_hoisted_1,_static_element_0)。 - 在原始的组件函数内部,用新创建的常量变量替换掉原来的 JSX 结构。
- 将该静态 JSX 子树对应的
3.2 静态 JSX 识别准则表格
| 特性 | 静态 JSX 示例 | 动态 JSX 示例 | 是否可提升 |
|---|---|---|---|
| 属性值 | <div className="foo"> |
<div className={isActive ? "active" : "inactive"}> |
否 |
<button disabled={false}> |
<button disabled={props.isDisabled}> |
否 | |
<span style={{ color: 'red' }}> |
<span style={getStyle(props.theme)}></span> |
否 | |
| 子元素 | <div>Hello World</div> |
<div>{props.message}</div> |
否 |
<div><span>Static</span></div> |
<div>{items.map(item => <p>{item.name}</p>)}</div> |
否 | |
<div>{someConstantValue}</div> |
<div>{Date.now()}</div> |
否 | |
ref 属性 |
不包含 ref |
<input ref={inputRef} /> |
否 |
key 属性 |
<li key="static-id"> |
<li key={item.id}> |
否 |
| 展开属性 | 不包含 {...} |
<div {...props}> |
否 |
| 自定义组件 | <MyStaticComponent> (假设 MyStaticComponent 内部全是静态) |
<UserCard user={props.user} /> |
否 (通常) |
| Context API | 不包含 <Context.Provider> 或 <Context.Consumer> |
<ThemeContext.Consumer>{...}</ThemeContext.Consumer> |
否 |
总结: 只要 JSX 节点或其子树的任何部分依赖于组件的 props、state、context、函数调用结果、Date.now() 或任何可能在不同渲染周期中发生变化的运行时值,它就不能被常量提升。它必须是完全自包含、自稳定的。
四、代码转换示例
我们来看一个具体的代码转换例子,以便更好地理解常量提升的效果。
4.1 原始 JSX 代码
假设我们有这样一个组件:
import React, { useState } from 'react';
function MyDashboard({ userName }) {
const [count, setCount] = useState(0);
const handleIncrement = () => {
setCount(prev => prev + 1);
};
return (
<div className="dashboard-layout">
{/* 这是一个完全静态的头部区域 */}
<header className="app-header">
<h1>Welcome to Dashboard</h1>
<nav>
<a href="/home">Home</a>
<a href="/settings">Settings</a>
<button onClick={() => alert('Help clicked!')}>Help</button>
</nav>
</header>
{/* 这是一个动态内容区域 */}
<main className="dashboard-content">
<p>Hello, {userName}!</p>
<p>Current count: {count}</p>
<button onClick={handleIncrement}>Increment</button>
</main>
{/* 这是一个完全静态的底部区域 */}
<footer className="app-footer">
<p>© 2023 My Company</p>
<p>All rights reserved.</p>
</footer>
</div>
);
}
export default MyDashboard;
在这个组件中,header 和 footer 区域的内容在任何情况下都不会改变,而 main 区域的内容会因为 userName props 或 count state 的变化而更新。
4.2 经过 Babel plugin-transform-react-jsx 后的代码(未进行常量提升)
为了更清晰地展示,我们假设是旧版 JSX 转换,即 React.createElement。
import React, { useState } from 'react';
function MyDashboard({ userName }) {
const [count, setCount] = useState(0);
const handleIncrement = () => {
setCount(prev => prev + 1);
};
return React.createElement(
"div",
{ className: "dashboard-layout" },
// header 部分
React.createElement(
"header",
{ className: "app-header" },
React.createElement("h1", null, "Welcome to Dashboard"),
React.createElement(
"nav",
null,
React.createElement("a", { href: "/home" }, "Home"),
React.createElement("a", { href: "/settings" }, "Settings"),
React.createElement(
"button",
{ onClick: () => alert('Help clicked!') },
"Help"
)
)
),
// main 部分
React.createElement(
"main",
{ className: "dashboard-content" },
React.createElement("p", null, "Hello, ", userName, "!"),
React.createElement("p", null, "Current count: ", count),
React.createElement("button", { onClick: handleIncrement }, "Increment")
),
// footer 部分
React.createElement(
"footer",
{ className: "app-footer" },
React.createElement("p", null, "xA9 2023 My Company"),
React.createElement("p", null, "All rights reserved.")
)
);
}
export default MyDashboard;
可以看到,header 和 footer 的所有 React.createElement 调用都位于 MyDashboard 函数内部。这意味着每当 count 变化导致 MyDashboard 重新渲染时,这些 createElement 调用都会再次执行,生成新的虚拟 DOM 对象。
4.3 经过常量提升后的代码
在经过 plugin-transform-react-constant-elements 等插件处理后,代码会变成这样:
import React, { useState } from 'react';
// ----------------------------------------------------------------
// 编译器在此处将静态 JSX 的 createElement 结果提升为常量
// 这些变量只在模块加载时被创建一次
const _hoisted_header = React.createElement(
"header",
{ className: "app-header" },
React.createElement("h1", null, "Welcome to Dashboard"),
React.createElement(
"nav",
null,
React.createElement("a", { href: "/home" }, "Home"),
React.createElement("a", { href: "/settings" }, "Settings"),
React.createElement(
"button",
{ onClick: () => alert('Help clicked!') }, // 注意:此处的函数是静态的,但如果函数内部引用了组件的动态状态,则整个元素可能无法提升
"Help"
)
)
); // _hoisted_header 及其子树只创建一次
const _hoisted_footer = React.createElement(
"footer",
{ className: "app-footer" },
React.createElement("p", null, "xA9 2023 My Company"),
React.createElement("p", null, "All rights reserved.")
); // _hoisted_footer 及其子树只创建一次
// ----------------------------------------------------------------
function MyDashboard({ userName }) {
const [count, setCount] = useState(0);
const handleIncrement = () => {
setCount(prev => prev + 1);
};
return React.createElement(
"div",
{ className: "dashboard-layout" },
// 直接引用已提升的常量
_hoisted_header,
// 动态部分保持不变
React.createElement(
"main",
{ className: "dashboard-content" },
React.createElement("p", null, "Hello, ", userName, "!"),
React.createElement("p", null, "Current count: ", count),
React.createElement("button", { onClick: handleIncrement }, "Increment")
),
// 直接引用已提升的常量
_hoisted_footer
);
}
export default MyDashboard;
关键变化:
_hoisted_header和_hoisted_footer这两个变量在MyDashboard函数的外部被定义。这意味着它们对应的React.createElement调用只会在MyDashboard模块第一次加载时执行一次。- 在
MyDashboard函数内部,原来header和footer的 JSX 结构被替换成了对_hoisted_header和_hoisted_footer的直接引用。
现在,每当 MyDashboard 组件重新渲染时,只有 main 部分的 createElement 调用会执行,而 header 和 footer 部分则直接复用之前创建好的虚拟 DOM 对象。
五、常量提升对 React 协调算法的加速作用
常量提升的最大优势之一在于它能够与 React 的协调算法(Reconciliation Algorithm)完美协同,显著加速渲染过程。
5.1 虚拟 DOM 协调的基础
React 的协调算法依赖于几个核心原则:
- 元素类型比较: 如果两个根元素类型不同(例如,
<div>变为<span>),React 会销毁旧树并从头开始构建新树。 - 同类型元素比较: 如果两个根元素类型相同,React 会保留 DOM 节点,只更新其属性。然后递归地比较它们的子元素。
- 列表和 Key: 当处理列表时,
key属性帮助 React 识别哪些子元素是新增、删除或移动的,从而优化列表项的更新。
5.2 严格相等检查的短路优化
常量提升的核心在于,它使得静态 JSX 节点在不同渲染周期中,其对应的虚拟 DOM 对象是严格相等的(即内存地址相同)。
考虑以下场景:
- 没有常量提升: 每次渲染,即使是静态的
header,也会生成一个新的虚拟 DOM 对象headerObjectA。下次渲染时,又会生成一个新的headerObjectB。尽管headerObjectA和headerObjectB在结构和内容上是相同的,但它们是两个不同的 JavaScript 对象 (headerObjectA !== headerObjectB)。因此,React 必须深入比较headerObjectA和headerObjectB的属性和子元素。 - 有常量提升: 静态
header对应的虚拟 DOM 对象_hoisted_header只创建一次。在每次渲染中,React 都会看到_hoisted_header的引用。当协调算法比较新旧树时,它会发现oldTree.children[0] === newTree.children[0](因为它们都指向同一个_hoisted_header对象)。
当 React 遇到两个严格相等的虚拟 DOM 节点时,它会触发一个非常重要的短路优化:立即判断该节点及其整个子树都没有发生变化,从而完全跳过对该子树的深度比较。
流程图解:
-
第一次渲染:
MyDashboard()执行,创建_hoisted_header对象。MyDashboard()返回{ type: 'div', children: [_hoisted_header, dynamicMain, _hoisted_footer] }- React 构建实际 DOM。
-
第二次渲染(
count变化):MyDashboard()再次执行。- 由于
_hoisted_header是常量,它直接引用第一次创建的那个对象。 dynamicMain部分因为count变化,会创建新的对象dynamicMain_new。_hoisted_footer也是常量,直接引用第一次创建的那个对象。MyDashboard()返回{ type: 'div', children: [_hoisted_header, dynamicMain_new, _hoisted_footer] }- 协调开始:
- 比较根
div:类型相同,继续比较子元素。 - 比较第一个子元素:
oldTree.children[0](即_hoisted_header) 与newTree.children[0](即_hoisted_header)。 - 严格相等! React 发现
_hoisted_header === _hoisted_header为true。 - 短路优化: React 立即知道
_hoisted_header及其整个子树都没有变化,完全跳过对header部分的任何进一步比较和 DOM 操作。 - 比较第二个子元素:
oldTree.children[1](即dynamicMain) 与newTree.children[1](即dynamicMain_new)。 - 不相等! React 深入比较
dynamicMain和dynamicMain_new的属性和子元素,发现count变化,并更新对应的 DOM。 - 比较第三个子元素:
oldTree.children[2](即_hoisted_footer) 与newTree.children[2](即_hoisted_footer)。 - 严格相等! React 再次短路,跳过对
footer部分的比较。
- 比较根
这种短路优化对于大型、复杂的静态子树来说,性能提升是巨大的。它避免了不必要的递归遍历和属性比较,将 O(N) 的比较复杂度降低到 O(1) 的引用比较。
六、与 React.memo 和 useMemo 的关系与区别
常量提升、React.memo 和 useMemo 都是 React 性能优化的手段,但它们在作用层级、实现方式和应用场景上有所不同。
6.1 React.memo
- 作用层级: 组件层级。
- 实现方式: HOC (高阶组件)。它包裹一个组件,如果组件的 props 在两次渲染之间没有发生变化(默认进行浅比较),则跳过组件的重新渲染。
- 何时发生: 运行时。
- 优化目标: 避免组件函数的重新执行。
- 与常量提升的关系:
React.memo优化的是组件的渲染。即使一个React.memo包裹的组件被跳过渲染,如果其内部有未被提升的静态 JSX,那么在组件 第一次 渲染或 props 发生变化导致其 重新渲染时,这些静态 JSX 仍然会重复创建虚拟 DOM 对象。- 常量提升发生在
React.memo之前(编译时)。如果React.memo组件内部的 JSX 包含可提升的静态部分,这些部分会被提升。这意味着,当React.memo组件因 props 变化而不得不重新渲染时,其内部的静态 JSX 仍然能享受到常量提升带来的好处(即复用已创建的虚拟 DOM 对象)。 - 它们是互补的优化。
React.memo减少了组件的执行次数,而常量提升减少了每次执行中创建虚拟 DOM 对象的次数和协调的开销。
6.2 useMemo
- 作用层级: 钩子 (Hook)。
- 实现方式: 缓存一个计算结果,只有当其依赖项发生变化时才重新计算。
- 何时发生: 运行时。
- 优化目标: 缓存昂贵的计算结果,包括 JSX 元素。
-
与常量提升的关系:
-
useMemo可以用来手动“提升”JSX 元素到组件内部的内存中,以避免每次渲染都重新创建它们。例如:function MyComponent() { const staticHeader = useMemo(() => ( <header><h1>App Title</h1></header> ), []); // 空依赖数组表示只创建一次 return ( <div> {staticHeader} {/* ... dynamic content ... */} </div> ); } - 这种手动
useMemo的方式与编译器的常量提升在效果上非常相似:都是为了让静态 JSX 对应的虚拟 DOM 对象只创建一次并被复用。 - 关键区别:
- 时机:
useMemo是在运行时执行的,其缓存逻辑和依赖项检查本身也有一定的运行时开销。常量提升是编译时完成的,它将createElement调用从运行时逻辑中完全移出,没有任何运行时开销。 - 自动化: 常量提升是编译器自动进行的,无需开发者手动干预。
useMemo需要开发者显式地编写和维护。 - 开销:
useMemo需要在每次渲染时检查依赖数组,并可能进行一次函数调用来生成 memoized 值。常量提升则完全消除了这些运行时操作,直接引用预先计算好的常量。
- 时机:
-
总结表格:
| 特性 | 常量提升 (Constant Hoisting) | React.memo |
useMemo (用于 JSX) |
|---|---|---|---|
| 作用层级 | JSX 节点/子树 | 组件 | 任何值 (包括 JSX 元素) |
| 实现方式 | 编译器转换 (Babel 插件) | HOC (高阶组件) | Hook |
| 发生时机 | 编译时 | 运行时 | 运行时 |
| 优化目标 | 避免静态虚拟 DOM 对象重复创建 | 避免组件函数重新执行 | 避免昂贵计算结果重复计算/JSX 对象重复创建 |
| 开销 | 几乎无运行时开销 | 浅比较 props 的运行时开销 | 依赖项比较和潜在函数执行的运行时开销 |
| 自动化 | 自动 | 需手动包裹组件 | 需手动编写 Hook |
| 配合使用 | 可以与 React.memo 和 useMemo 互补 |
包裹的组件内部可受益于常量提升 | 可用于手动实现类似常量提升的效果 |
因此,对于完全静态的 JSX 节点,编译器自动的常量提升是最理想、最无开销的优化方式。只有当编译器无法识别为静态(例如,JSX 结构本身依赖于运行时逻辑),但其结果又相对稳定时,才考虑手动使用 useMemo。
七、实践意义与最佳实践
作为开发者,我们通常不需要直接配置或干预常量提升的过程,因为现代的 React 项目脚手架(如 Create React App、Next.js)已经默认集成了相关的 Babel 插件和配置。但是,理解它的工作原理能够帮助我们写出更具性能意识的代码。
- 倾向于静态 JSX 结构: 尽可能地将 UI 中的静态部分声明为纯粹的 JSX,避免不必要的动态属性或子元素。例如,如果一个
className永远是"my-class",就直接写死,而不是className={someCondition ? "my-class" : "my-class"}。 - 避免无谓的动态化: 只有在确实需要响应状态或 props 变化时才引入动态逻辑。如果一个
div的key总是固定值,就直接赋给它一个静态字符串key="some-id",而不是key={generateId()}。 - 理解 JSX
key的重要性: 动态的key属性会阻止常量提升,因为key告诉 React 元素可能不是同一个。只有当列表项确实是动态生成且其顺序或内容可能变化时,才使用动态key。对于静态列表,如果每个项都独一无二,可以使用静态key。 onClick等事件处理函数: 在上述示例中,onClick={() => alert('Help clicked!')}的函数是一个匿名函数,在每次渲染时都会重新创建。但由于它是一个“常量”函数(没有引用props或state),在某些更积极的优化(如 React Forget 或更激进的 Babel 配置)下,也可能被提升。在当前主流的plugin-transform-react-constant-elements下,如果一个静态元素包含动态的事件处理函数,该元素通常仍然可以被提升,因为事件处理函数本身是作为属性传递的,但该函数本身在每次渲染时仍然会创建。对于性能敏感的事件处理,可以考虑使用useCallback。- React Forget 的未来: 值得一提的是,React 团队正在开发一款名为 React Forget 的编译器,其目标是实现更全面的自动优化,包括自动 memoization 和更智能的常量提升,使得开发者无需手动使用
useMemo或useCallback就能获得最佳性能。常量提升正是这种更宏大编译器优化愿景的一个重要组成部分。
八、总结与展望
常量提升是 React 幕后一项强大而隐蔽的性能优化技术。它通过编译时对静态 JSX 节点进行识别和提升,将虚拟 DOM 对象的创建从每次组件渲染中剥离出来,使其只在模块加载时创建一次。这项优化带来了显著的性能收益,包括减少 createElement 调用、降低内存分配、加速 React 协调算法的短路优化,并减轻了垃圾回收的压力。
作为开发者,我们无需直接干预这项优化,但理解其原理有助于我们编写更符合 React 优化策略的代码。未来,随着 React 编译器技术(如 React Forget)的不断发展,这些性能优化将变得更加自动化和智能化,进一步提升开发体验和应用性能。这项技术是 React 生态系统持续演进的绝佳例证,它在不增加开发者心智负担的前提下,悄然为我们的应用注入了强大的性能动力。