react-performance-optimizer
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseReact Performance Optimizer
React 性能优化工具
Expert in diagnosing and fixing React performance issues to achieve buttery-smooth 60fps experiences.
专注于诊断和修复React性能问题,以实现丝滑流畅的60fps体验。
When to Use
适用场景
✅ Use for:
- Slow component re-renders
- Large lists (>100 items) causing lag
- Bundle size >500KB (gzipped)
- Time to Interactive >3 seconds
- Janky scrolling or animations
- Memory leaks from unmounted components
❌ NOT for:
- Apps with <10 components (premature optimization)
- Backend API slowness (fix the API)
- Network latency (use caching/CDN)
- Non-React frameworks (use framework-specific tools)
✅ 适用情况:
- 组件重渲染缓慢
- 大型列表(>100条数据)导致卡顿
- 打包体积超过500KB(gzip压缩后)
- 可交互时间超过3秒
- 滚动或动画出现卡顿
- 未卸载组件引发内存泄漏
❌ 不适用情况:
- 组件数量少于10个的应用(过早优化)
- 后端API响应缓慢(应优化API本身)
- 网络延迟(使用缓存/CDN解决)
- 非React框架(使用对应框架的专属工具)
Quick Decision Tree
快速决策树
Is your React app slow?
├── Profiler shows >16ms renders? → Use memoization
├── Lists with >100 items? → Use virtualization
├── Bundle size >500KB? → Code splitting
├── Lighthouse score <70? → Multiple optimizations
└── Feels fast enough? → Don't optimize yet你的React应用是否运行缓慢?
├── 性能分析工具显示渲染耗时>16ms? → 使用记忆化处理
├── 列表数据超过100条? → 使用虚拟化
├── 打包体积>500KB? → 进行代码分割
├── Lighthouse评分<70? → 组合多种优化手段
└── 运行速度已足够流畅? → 暂时无需优化Technology Selection
技术选型
Performance Tools (2024)
2024年性能优化工具
| Tool | Purpose | When to Use |
|---|---|---|
| React DevTools Profiler | Find slow components | Always start here |
| Lighthouse | Overall performance score | Before/after comparison |
| webpack-bundle-analyzer | Identify large dependencies | Bundle >500KB |
| why-did-you-render | Unnecessary re-renders | Debug re-render storms |
| React Compiler (2024+) | Automatic memoization | React 19+ |
Timeline:
- 2018: React.memo, useMemo, useCallback introduced
- 2020: Concurrent Mode (now Concurrent Rendering)
- 2022: Automatic batching in React 18
- 2024: React Compiler (automatic optimization)
- 2025+: React Compiler expected to replace manual memoization
| 工具 | 用途 | 适用场景 |
|---|---|---|
| React DevTools Profiler | 定位慢渲染组件 | 始终从这里开始排查 |
| Lighthouse | 整体性能评分 | 优化前后的对比分析 |
| webpack-bundle-analyzer | 识别大型依赖包 | 打包体积>500KB时 |
| why-did-you-render | 排查不必要的重渲染 | 调试重渲染风暴问题 |
| React Compiler (2024+) | 自动记忆化处理 | React 19及以上版本 |
时间线:
- 2018年:推出React.memo、useMemo、useCallback
- 2020年:并发模式(现称并发渲染)
- 2022年:React 18中实现自动批处理
- 2024年:React Compiler(自动优化)
- 2025年及以后:React Compiler有望替代手动记忆化处理
Common Anti-Patterns
常见反模式
Anti-Pattern 1: Premature Memoization
反模式1:过早的记忆化处理
Novice thinking: "Wrap everything in useMemo for speed"
Problem: Adds complexity and overhead for negligible gains.
Wrong approach:
typescript
// ❌ Over-optimization
function UserCard({ user }) {
const fullName = useMemo(() => `${user.first} ${user.last}`, [user]);
const age = useMemo(() => new Date().getFullYear() - user.birthYear, [user]);
return <div>{fullName}, {age}</div>;
}Why wrong: String concatenation is faster than useMemo overhead.
Correct approach:
typescript
// ✅ Simple is fast
function UserCard({ user }) {
const fullName = `${user.first} ${user.last}`;
const age = new Date().getFullYear() - user.birthYear;
return <div>{fullName}, {age}</div>;
}Rule of thumb: Only memoize if:
- Computation takes >5ms (use Profiler to measure)
- Result used in dependency array
- Prevents child re-renders
新手误区:「把所有内容都用useMemo包裹来提升速度」
问题:增加复杂度和额外开销,收益却微乎其微。
错误示例:
typescript
// ❌ 过度优化
function UserCard({ user }) {
const fullName = useMemo(() => `${user.first} ${user.last}`, [user]);
const age = useMemo(() => new Date().getFullYear() - user.birthYear, [user]);
return <div>{fullName}, {age}</div>;
}问题原因:字符串拼接的速度比useMemo的额外开销更快。
正确示例:
typescript
// ✅ 简洁即快速
function UserCard({ user }) {
const fullName = `${user.first} ${user.last}`;
const age = new Date().getFullYear() - user.birthYear;
return <div>{fullName}, {age}</div>;
}经验法则:仅在以下情况使用记忆化:
- 计算耗时超过5ms(用性能分析工具测量)
- 结果用于依赖数组中
- 能避免子组件重渲染
Anti-Pattern 2: Not Memoizing Callbacks
反模式2:不对回调函数进行记忆化
Problem: New function instance on every render breaks React.memo.
Wrong approach:
typescript
// ❌ Child re-renders on every parent render
function Parent() {
const [count, setCount] = useState(0);
return (
<Child onUpdate={() => setCount(count + 1)} />
);
}
const Child = React.memo(({ onUpdate }) => {
return <button onClick={onUpdate}>Update</button>;
});Why wrong: Arrow function creates new reference → React.memo useless.
Correct approach:
typescript
// ✅ Stable callback reference
function Parent() {
const [count, setCount] = useState(0);
const handleUpdate = useCallback(() => {
setCount(c => c + 1); // Updater function avoids dependency
}, []);
return <Child onUpdate={handleUpdate} />;
}
const Child = React.memo(({ onUpdate }) => {
return <button onClick={onUpdate}>Update</button>;
});问题:每次渲染都创建新的函数实例,导致React.memo失效。
错误示例:
typescript
// ❌ 父组件每次渲染都会触发子组件重渲染
function Parent() {
const [count, setCount] = useState(0);
return (
<Child onUpdate={() => setCount(count + 1)} />
);
}
const Child = React.memo(({ onUpdate }) => {
return <button onClick={onUpdate}>Update</button>;
});问题原因:箭头函数会创建新的引用 → React.memo失去作用。
正确示例:
typescript
// ✅ 稳定的回调引用
function Parent() {
const [count, setCount] = useState(0);
const handleUpdate = useCallback(() => {
setCount(c => c + 1); // 更新器函数避免依赖问题
}, []);
return <Child onUpdate={handleUpdate} />;
}
const Child = React.memo(({ onUpdate }) => {
return <button onClick={onUpdate}>Update</button>;
});Anti-Pattern 3: Rendering Large Lists Without Virtualization
反模式3:渲染大型列表时不使用虚拟化
Problem: Rendering 1000+ DOM nodes causes lag.
Symptom: Scrolling feels janky, initial render slow.
Wrong approach:
typescript
// ❌ Renders all 10,000 items
function UserList({ users }) {
return (
<div>
{users.map(user => (
<UserCard key={user.id} user={user} />
))}
</div>
);
}Correct approach:
typescript
// ✅ Only renders visible items
import { FixedSizeList } from 'react-window';
function UserList({ users }) {
return (
<FixedSizeList
height={600}
itemCount={users.length}
itemSize={50}
width="100%"
>
{({ index, style }) => (
<div style={style}>
<UserCard user={users[index]} />
</div>
)}
</FixedSizeList>
);
}Impact: 10,000 items: 5 seconds → 50ms render time.
问题:渲染1000+个DOM节点会导致卡顿。
症状:滚动时感觉卡顿,初始渲染缓慢。
错误示例:
typescript
// ❌ 渲染全部10,000条数据
function UserList({ users }) {
return (
<div>
{users.map(user => (
<UserCard key={user.id} user={user} />
))}
</div>
);
}正确示例:
typescript
// ✅ 仅渲染可见的条目
import { FixedSizeList } from 'react-window';
function UserList({ users }) {
return (
<FixedSizeList
height={600}
itemCount={users.length}
itemSize={50}
width="100%"
>
{({ index, style }) => (
<div style={style}>
<UserCard user={users[index]} />
</div>
)}
</FixedSizeList>
);
}优化效果:10,000条数据:渲染时间从5秒缩短至50ms。
Anti-Pattern 4: No Code Splitting
反模式4:不进行代码分割
Problem: 2MB bundle downloaded upfront, slow initial load.
Wrong approach:
typescript
// ❌ Everything in main bundle
import AdminPanel from './AdminPanel'; // 500KB
import Dashboard from './Dashboard';
import Settings from './Settings';
function App() {
return (
<Routes>
<Route path="/admin" element={<AdminPanel />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Routes>
);
}Correct approach:
typescript
// ✅ Lazy load routes
import { lazy, Suspense } from 'react';
const AdminPanel = lazy(() => import('./AdminPanel'));
const Dashboard = lazy(() => import('./Dashboard'));
const Settings = lazy(() => import('./Settings'));
function App() {
return (
<Suspense fallback={<Loading />}>
<Routes>
<Route path="/admin" element={<AdminPanel />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
);
}Impact: Initial bundle: 2MB → 300KB.
问题:前端一次性下载2MB的打包文件,初始加载缓慢。
错误示例:
typescript
// ❌ 所有内容都在主打包文件中
import AdminPanel from './AdminPanel'; // 500KB
import Dashboard from './Dashboard';
import Settings from './Settings';
function App() {
return (
<Routes>
<Route path="/admin" element={<AdminPanel />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Routes>
);
}正确示例:
typescript
// ✅ 懒加载路由
import { lazy, Suspense } from 'react';
const AdminPanel = lazy(() => import('./AdminPanel'));
const Dashboard = lazy(() => import('./Dashboard'));
const Settings = lazy(() => import('./Settings'));
function App() {
return (
<Suspense fallback={<Loading />}>
<Routes>
<Route path="/admin" element={<AdminPanel />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
);
}优化效果:初始打包体积从2MB减少至300KB。
Anti-Pattern 5: Expensive Operations in Render
反模式5:在渲染函数中执行昂贵操作
Problem: Heavy computation on every render.
Wrong approach:
typescript
// ❌ Sorts on every render (even when data unchanged)
function ProductList({ products }) {
const sorted = products.sort((a, b) => b.price - a.price);
return <div>{sorted.map(p => <Product product={p} />)}</div>;
}Correct approach:
typescript
// ✅ Memoize expensive operation
function ProductList({ products }) {
const sorted = useMemo(
() => [...products].sort((a, b) => b.price - a.price),
[products]
);
return <div>{sorted.map(p => <Product product={p} />)}</div>;
}问题:每次渲染都执行大量计算。
错误示例:
typescript
// ❌ 每次渲染都排序(即使数据未变化)
function ProductList({ products }) {
const sorted = products.sort((a, b) => b.price - a.price);
return <div>{sorted.map(p => <Product product={p} />)}</div>;
}正确示例:
typescript
// ✅ 对昂贵操作进行记忆化
function ProductList({ products }) {
const sorted = useMemo(
() => [...products].sort((a, b) => b.price - a.price),
[products]
);
return <div>{sorted.map(p => <Product product={p} />)}</div>;
}Implementation Patterns
实现模式
Pattern 1: React.memo for Pure Components
模式1:为纯组件使用React.memo
typescript
// Prevent re-render when props unchanged
const ExpensiveComponent = React.memo(({ data }) => {
// Complex rendering logic
return <div>{/* ... */}</div>;
});
// With custom comparison
const UserCard = React.memo(
({ user }) => <div>{user.name}</div>,
(prevProps, nextProps) => {
// Return true if props equal (skip re-render)
return prevProps.user.id === nextProps.user.id;
}
);typescript
// 当props未变化时阻止重渲染
const ExpensiveComponent = React.memo(({ data }) => {
// 复杂渲染逻辑
return <div>{/* ... */}</div>;
});
// 自定义比较逻辑
const UserCard = React.memo(
({ user }) => <div>{user.name}</div>,
(prevProps, nextProps) => {
// 如果props相等则返回true(跳过重渲染)
return prevProps.user.id === nextProps.user.id;
}
);Pattern 2: useMemo for Expensive Calculations
模式2:为昂贵计算使用useMemo
typescript
function DataTable({ rows, columns }) {
const sortedAndFiltered = useMemo(() => {
console.log('Recomputing...'); // Only logs when rows/columns change
return rows
.filter(row => row.visible)
.sort((a, b) => a.timestamp - b.timestamp);
}, [rows, columns]);
return <Table data={sortedAndFiltered} />;
}typescript
function DataTable({ rows, columns }) {
const sortedAndFiltered = useMemo(() => {
console.log('重新计算...'); // 仅在rows/columns变化时打印
return rows
.filter(row => row.visible)
.sort((a, b) => a.timestamp - b.timestamp);
}, [rows, columns]);
return <Table data={sortedAndFiltered} />;
}Pattern 3: useCallback for Stable References
模式3:为稳定引用使用useCallback
typescript
function SearchBox({ onSearch }) {
const [query, setQuery] = useState('');
// Stable reference, doesn't break child memoization
const handleSubmit = useCallback(() => {
onSearch(query);
}, [query, onSearch]);
return (
<form onSubmit={handleSubmit}>
<input value={query} onChange={e => setQuery(e.target.value)} />
</form>
);
}typescript
function SearchBox({ onSearch }) {
const [query, setQuery] = useState('');
// 稳定的引用,不会破坏子组件的记忆化
const handleSubmit = useCallback(() => {
onSearch(query);
}, [query, onSearch]);
return (
<form onSubmit={handleSubmit}>
<input value={query} onChange={e => setQuery(e.target.value)} />
</form>
);
}Pattern 4: Virtualization (react-window)
模式4:虚拟化(react-window)
typescript
import { VariableSizeList } from 'react-window';
function MessageList({ messages }) {
const getItemSize = (index) => {
// Dynamic heights based on content
return messages[index].text.length > 100 ? 80 : 50;
};
return (
<VariableSizeList
height={600}
itemCount={messages.length}
itemSize={getItemSize}
width="100%"
>
{({ index, style }) => (
<div style={style}>
<Message message={messages[index]} />
</div>
)}
</VariableSizeList>
);
}typescript
import { VariableSizeList } from 'react-window';
function MessageList({ messages }) {
const getItemSize = (index) => {
// 根据内容动态设置高度
return messages[index].text.length > 100 ? 80 : 50;
};
return (
<VariableSizeList
height={600}
itemCount={messages.length}
itemSize={getItemSize}
width="100%"
>
{({ index, style }) => (
<div style={style}>
<Message message={messages[index]} />
</div>
)}
</VariableSizeList>
);
}Pattern 5: Code Splitting with React.lazy
模式5:使用React.lazy进行代码分割
typescript
// Route-based splitting
const routes = [
{ path: '/home', component: lazy(() => import('./Home')) },
{ path: '/about', component: lazy(() => import('./About')) },
{ path: '/contact', component: lazy(() => import('./Contact')) }
];
// Component-based splitting
const HeavyChart = lazy(() => import('./HeavyChart'));
function Dashboard() {
const [showChart, setShowChart] = useState(false);
return (
<div>
<button onClick={() => setShowChart(true)}>Show Chart</button>
{showChart && (
<Suspense fallback={<Spinner />}>
<HeavyChart />
</Suspense>
)}
</div>
);
}typescript
// 基于路由的分割
const routes = [
{ path: '/home', component: lazy(() => import('./Home')) },
{ path: '/about', component: lazy(() => import('./About')) },
{ path: '/contact', component: lazy(() => import('./Contact')) }
];
// 基于组件的分割
const HeavyChart = lazy(() => import('./HeavyChart'));
function Dashboard() {
const [showChart, setShowChart] = useState(false);
return (
<div>
<button onClick={() => setShowChart(true)}>显示图表</button>
{showChart && (
<Suspense fallback={<Spinner />}>
<HeavyChart />
</Suspense>
)}
</div>
);
}Production Checklist
生产环境检查清单
□ Profiler analysis completed (identified slow components)
□ Large lists use virtualization (>100 items)
□ Routes code-split with React.lazy
□ Heavy components lazy-loaded
□ Callbacks memoized with useCallback
□ Expensive computations use useMemo
□ Pure components wrapped in React.memo
□ Bundle analyzed (no duplicate dependencies)
□ Tree-shaking enabled (ESM imports)
□ Images optimized and lazy-loaded
□ Lighthouse score >90
□ Time to Interactive <3 seconds□ 已完成性能分析(定位到慢渲染组件)
□ 大型列表使用了虚拟化(>100条数据)
□ 路由使用React.lazy进行代码分割
□ 重型组件已懒加载
□ 回调函数使用useCallback进行记忆化
□ 昂贵计算使用useMemo处理
□ 纯组件已用React.memo包裹
□ 已分析打包文件(无重复依赖)
□ 已启用Tree-shaking(使用ESM导入)
□ 图片已优化并懒加载
□ Lighthouse评分>90
□ 可交互时间<3秒When to Use vs Avoid
优化场景判断
| Scenario | Optimize? |
|---|---|
| Rendering 1000+ list items | ✅ Yes - virtualize |
| Sorting/filtering large arrays | ✅ Yes - useMemo |
| Passing callbacks to memoized children | ✅ Yes - useCallback |
| String concatenation | ❌ No - fast enough |
| Simple arithmetic | ❌ No - don't memoize |
| 10-item list | ❌ No - premature optimization |
| 场景 | 是否需要优化? |
|---|---|
| 渲染1000+条列表数据 | ✅ 是 - 使用虚拟化 |
| 对大型数组排序/过滤 | ✅ 是 - 使用useMemo |
| 向记忆化子组件传递回调 | ✅ 是 - 使用useCallback |
| 字符串拼接 | ❌ 否 - 速度足够快 |
| 简单算术运算 | ❌ 否 - 无需记忆化 |
| 10条数据的列表 | ❌ 否 - 过早优化 |
References
参考资料
- - How to use React DevTools Profiler
/references/profiling-guide.md - - Reduce bundle size strategies
/references/bundle-optimization.md - - Detect and fix memory leaks
/references/memory-leaks.md
- - 如何使用React DevTools Profiler
/references/profiling-guide.md - - 减少打包体积的策略
/references/bundle-optimization.md - - 检测和修复内存泄漏
/references/memory-leaks.md
Scripts
脚本
- - Automated performance checks
scripts/performance_audit.ts - - Analyze and visualize bundle
scripts/bundle_analyzer.sh
This skill guides: React performance optimization | Memoization | Virtualization | Code splitting | Bundle optimization | Profiling
- - 自动化性能检查
scripts/performance_audit.ts - - 分析并可视化打包文件
scripts/bundle_analyzer.sh
本技能涵盖:React性能优化 | 记忆化 | 虚拟化 | 代码分割 | 打包优化 | 性能分析