
当一个 React 项目从十几个页面增长到上百个页面后,性能问题往往不是突然出现的。它更像温水煮青蛙:列表逐渐变长、状态越来越多、组件层级越来越深,直到某一天,用户开始反馈“点一下要等半天”。
这篇文章不讨论玄学优化,而是分享一套可以重复执行的方法:先测量,定位真正瓶颈,再用尽可能小的改动解决问题。
一、先确定问题到底在哪里
“页面很卡”不是一个可以直接解决的问题。我们需要先把它拆成更具体的现象:
- 首次打开页面很慢,可能是资源体积或接口响应问题;
- 输入框打字卡顿,可能是状态更新引起大范围重新渲染;
- 长列表滚动掉帧,可能是同时挂载了太多 DOM 节点;
- 切换标签页停顿,可能是复杂组件反复初始化;
- 数据更新后整个页面闪动,可能是组件边界设计不合理。
可以先使用浏览器 Performance 面板观察主线程,再使用 React DevTools Profiler 查看哪些组件渲染次数最多、耗时最长。

二、减少没有必要的重复渲染
假设父组件中有一个很大的列表,只要搜索框内容变化,所有行组件都会重新执行。我们可以先稳定传入的属性,再考虑使用 memo。
import { memo, useCallback, useState } from "react";
const UserRow = memo(function UserRow({ user, onSelect }) {
return (
<button onClick={() => onSelect(user.id)}>
{user.name}
</button>
);
});
export function UserList({ users }) {
const [keyword, setKeyword] = useState("");
const handleSelect = useCallback((id: string) => {
console.log("selected", id);
}, []);
return (
<section>
<input
value={keyword}
onChange={(event) => setKeyword(event.target.value)}
/>
{users.map((user) => (
<UserRow key={user.id} user={user} onSelect={handleSelect} />
))}
</section>
);
}
需要注意,memo 不是越多越好。如果组件本身非常简单,比较属性的成本可能和重新渲染差不多。是否使用它,应当由 Profiler 数据决定。
三、把昂贵计算从渲染过程里移开
筛选、排序或数据转换如果每次渲染都执行,数据量较大时会明显拖慢页面。此时可以使用 useMemo 缓存计算结果。
const visibleUsers = useMemo(() => {
const normalized = keyword.trim().toLowerCase();
return users
.filter((user) => user.name.toLowerCase().includes(normalized))
.sort((a, b) => a.name.localeCompare(b.name));
}, [users, keyword]);
缓存的关键不是“把计算包起来”,而是确保依赖稳定。如果 users 每次都被重新创建,缓存仍会失效。
四、长列表优先使用虚拟化
当列表拥有几千条记录时,即使每一行组件都很简单,同时创建几千个 DOM 节点也会带来压力。虚拟列表只渲染当前可见区域及少量缓冲项,是解决这类问题最有效的方式之一。
虚拟化通常比逐个优化行组件更有价值,因为它直接减少了需要创建、布局和绘制的节点数量。
不过虚拟列表也有代价:动态高度、键盘导航、滚动定位和无障碍支持都会变复杂。因此,数据量较小时没有必要过早引入。
五、按页面拆分代码
如果用户进入首页时就下载了所有后台页面代码,首次加载自然会变慢。可以使用动态导入按路由拆分:
import { lazy, Suspense } from "react";
const ReportPage = lazy(() => import("./pages/ReportPage"));
export function App() {
return (
<Suspense fallback={<div>页面加载中...</div>}>
<ReportPage />
</Suspense>
);
}
拆分粒度也不能无限缩小。请求数量过多会增加调度成本。通常先按路由拆分,再根据打包分析结果处理特别大的功能模块。
六、优化完成后必须重新测量
每次优化之后,至少要回答三个问题:
- 原来的瓶颈是否真的消失了?
- 是否给其他交互带来了新的问题?
- 优化代码增加的复杂度是否值得?
建议记录优化前后的关键数据,例如首次内容显示时间、交互延迟、组件提交耗时和长任务数量。没有数据对比,就无法判断修改究竟是优化,还是心理安慰。
总结
React 性能优化的核心不是堆叠 memo、useMemo 和 useCallback,而是形成稳定的排查流程:
测量问题 → 定位瓶颈 → 最小化修改 → 再次验证
对于大型项目,真正有效的优化通常来自更清晰的状态边界、更合理的组件结构、更少的无效工作,以及对性能数据的持续关注。