component-optimization
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseComponent Optimization
组件优化
Master React performance optimization techniques. Learn how to identify and fix performance bottlenecks, prevent unnecessary re-renders, and create fast, responsive UIs.
掌握React性能优化技巧。学习如何识别和修复性能瓶颈,避免不必要的重渲染,打造快速、响应式的UI。
Quick Reference
快速参考
Prevent Re-renders with React.memo
使用React.memo避免重渲染
typescript
const MemoizedComponent = React.memo(function Component({ data }) {
return <div>{data}</div>;
});typescript
const MemoizedComponent = React.memo(function Component({ data }) {
return <div>{data}</div>;
});Memoize Expensive Calculations
缓存昂贵计算结果
typescript
const result = useMemo(() =>
expensiveCalculation(data),
[data]
);typescript
const result = useMemo(() =>
expensiveCalculation(data),
[data]
);Memoize Callback Functions
缓存回调函数
typescript
const handleClick = useCallback(() => {
doSomething(value);
}, [value]);typescript
const handleClick = useCallback(() => {
doSomething(value);
}, [value]);Code Splitting
代码分割
typescript
const LazyComponent = lazy(() => import('./Component'));
<Suspense fallback={<Spinner />}>
<LazyComponent />
</Suspense>typescript
const LazyComponent = lazy(() => import('./Component'));
<Suspense fallback={<Spinner />}>
<LazyComponent />
</Suspense>When to Use This Skill
何时使用这些技巧
- Component renders slowly or causes UI lag
- Large lists or tables perform poorly
- Callbacks cause child components to re-render
- Initial page load is too slow
- Bundle size is too large
- Profiler shows unnecessary re-renders
- 组件渲染缓慢或导致UI卡顿
- 大型列表或表格性能不佳
- 回调函数导致子组件重渲染
- 初始页面加载过慢
- 包体积过大
- 性能分析器显示存在不必要的重渲染
Understanding Re-renders
理解重渲染
When Does React Re-render?
React何时会重渲染?
React re-renders a component when:
- State changes - or
useStateupdatesuseReducer - Props change - Parent passes new props
- Parent re-renders - By default, all children re-render
- Context changes - Any context value updates
当以下情况发生时,React会重渲染组件:
- 状态变更 - 或
useState更新useReducer - Props变更 - 父组件传递新的props
- 父组件重渲染 - 默认情况下,所有子组件都会重渲染
- Context变更 - 任何Context值更新
The Re-render Problem
重渲染问题
tsx
// ❌ PROBLEM: Child re-renders unnecessarily
function Parent() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
<ExpensiveChild /> {/* Re-renders every time! */}
</div>
);
}Even though doesn't use , it re-renders whenever the parent does.
ExpensiveChildcounttsx
// ❌ 问题:子组件不必要地重渲染
function Parent() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
<ExpensiveChild /> {/* 每次都会重渲染! */}
</div>
);
}即使不使用,它也会在父组件重渲染时跟着重渲染。
ExpensiveChildcountOptimization Techniques
优化技巧
1. React.memo - Prevent Unnecessary Re-renders
1. React.memo - 避免不必要的重渲染
React.memotsx
// ✅ SOLUTION: Memoize the child
const ExpensiveChild = React.memo(function ExpensiveChild() {
console.log("ExpensiveChild rendered");
return (
<div>
{/* Expensive rendering logic */}
</div>
);
});
function Parent() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
<ExpensiveChild /> {/* No longer re-renders! */}
</div>
);
}When to use:
- Component renders often but props rarely change
- Component has expensive rendering logic
- Component is large with many children
When NOT to use:
- Props change frequently
- Component is simple and fast
- Premature optimization
React.memotsx
// ✅ 解决方案:缓存子组件
const ExpensiveChild = React.memo(function ExpensiveChild() {
console.log("ExpensiveChild rendered");
return (
<div>
{/* 昂贵的渲染逻辑 */}
</div>
);
});
function Parent() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
<ExpensiveChild /> {/* 不再重渲染! */}
</div>
);
}适用场景:
- 组件频繁渲染但props很少变更
- 组件包含昂贵的渲染逻辑
- 组件较大且包含多个子组件
不适用场景:
- props频繁变更
- 组件简单且渲染快速
- 过早优化
2. useMemo - Memoize Expensive Calculations
2. useMemo - 缓存昂贵计算结果
useMemotsx
function ProductList({ products, filters }) {
// ❌ Recalculates on every render
const filtered = products.filter(p =>
p.category === filters.category
);
// ✅ Only recalculates when dependencies change
const filtered = useMemo(
() => products.filter(p => p.category === filters.category),
[products, filters.category]
);
return (
<ul>
{filtered.map(product => (
<li key={product.id}>{product.name}</li>
))}
</ul>
);
}When to use:
- Expensive calculations (filtering, sorting, mapping large arrays)
- Derived state from props or state
- Creating objects/arrays passed as props to memoized children
When NOT to use:
- Simple calculations (they're fast enough)
- Values that change every render anyway
- Premature optimization
useMemotsx
function ProductList({ products, filters }) {
// ❌ 每次渲染都会重新计算
const filtered = products.filter(p =>
p.category === filters.category
);
// ✅ 仅当依赖项变更时重新计算
const filtered = useMemo(
() => products.filter(p => p.category === filters.category),
[products, filters.category]
);
return (
<ul>
{filtered.map(product => (
<li key={product.id}>{product.name}</li>
))}
</ul>
);
}适用场景:
- 昂贵的计算(过滤、排序、映射大型数组)
- 从props或state派生的状态
- 创建作为props传递给已缓存子组件的对象/数组
不适用场景:
- 简单计算(速度足够快)
- 每次渲染都会变更的值
- 过早优化
3. useCallback - Memoize Functions
3. useCallback - 缓存函数
useCallbacktsx
function TodoList({ todos }) {
const [filter, setFilter] = useState("all");
// ❌ New function every render, breaks React.memo
const handleComplete = (id) => {
completeTodo(id);
};
// ✅ Same function reference between renders
const handleComplete = useCallback((id) => {
completeTodo(id);
}, []);
return (
<ul>
{todos.map(todo => (
<MemoizedTodoItem
key={todo.id}
todo={todo}
onComplete={handleComplete}
/>
))}
</ul>
);
}When to use:
- Passing callbacks to memoized children
- Functions used in dependency arrays
- Functions passed to many children
When NOT to use:
- Functions not passed as props
- Parent component re-renders frequently anyway
- Simple event handlers
useCallbacktsx
function TodoList({ todos }) {
const [filter, setFilter] = useState("all");
// ❌ 每次渲染创建新函数,破坏React.memo的效果
const handleComplete = (id) => {
completeTodo(id);
};
// ✅ 渲染之间保持相同的函数引用
const handleComplete = useCallback((id) => {
completeTodo(id);
}, []);
return (
<ul>
{todos.map(todo => (
<MemoizedTodoItem
key={todo.id}
todo={todo}
onComplete={handleComplete}
/>
))}
</ul>
);
}适用场景:
- 向已缓存的子组件传递回调函数
- 在依赖数组中使用的函数
- 传递给多个子组件的函数
不适用场景:
- 未作为props传递的函数
- 父组件本身频繁重渲染
- 简单事件处理程序
4. Code Splitting with React.lazy
4. 使用React.lazy进行代码分割
Split large components into separate bundles that load on demand.
tsx
import { lazy, Suspense } from "react";
// ✅ Only loads when needed
const AdminPanel = lazy(() => import("./AdminPanel"));
const Charts = lazy(() => import("./Charts"));
function Dashboard() {
const [showAdmin, setShowAdmin] = useState(false);
return (
<div>
<h1>Dashboard</h1>
{showAdmin && (
<Suspense fallback={<Spinner />}>
<AdminPanel />
</Suspense>
)}
<Suspense fallback={<ChartsSkeleton />}>
<Charts />
</Suspense>
</div>
);
}When to use:
- Large components not needed initially
- Admin/authenticated sections
- Modals and dialogs
- Route-based code splitting
将大型组件拆分为按需加载的独立包。
tsx
import { lazy, Suspense } from "react";
// ✅ 仅在需要时加载
const AdminPanel = lazy(() => import("./AdminPanel"));
const Charts = lazy(() => import("./Charts"));
function Dashboard() {
const [showAdmin, setShowAdmin] = useState(false);
return (
<div>
<h1>Dashboard</h1>
{showAdmin && (
<Suspense fallback={<Spinner />}>
<AdminPanel />
</Suspense>
)}
<Suspense fallback={<ChartsSkeleton />}>
<Charts />
</Suspense>
</div>
);
}适用场景:
- 初始加载不需要的大型组件
- 管理员/认证专属区域
- 模态框和对话框
- 基于路由的代码分割
5. Virtualization for Long Lists
5. 长列表虚拟化
Render only visible items in long lists.
tsx
import { useVirtualizer } from "@tanstack/react-virtual";
function VirtualList({ items }) {
const parentRef = useRef<HTMLDivElement>(null);
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 50,
});
return (
<div ref={parentRef} style={{ height: "400px", overflow: "auto" }}>
<div style={{ height: `${virtualizer.getTotalSize()}px` }}>
{virtualizer.getVirtualItems().map((virtualItem) => (
<div
key={virtualItem.key}
style={{
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: `${virtualItem.size}px`,
transform: `translateY(${virtualItem.start}px)`,
}}
>
{items[virtualItem.index].name}
</div>
))}
</div>
</div>
);
}When to use:
- Lists with 100+ items
- Tables with many rows
- Infinite scrolling
- Chat messages or feeds
仅渲染长列表中可见的项。
tsx
import { useVirtualizer } from "@tanstack/react-virtual";
function VirtualList({ items }) {
const parentRef = useRef<HTMLDivElement>(null);
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 50,
});
return (
<div ref={parentRef} style={{ height: "400px", overflow: "auto" }}>
<div style={{ height: `${virtualizer.getTotalSize()}px` }}>
{virtualizer.getVirtualItems().map((virtualItem) => (
<div
key={virtualItem.key}
style={{
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: `${virtualItem.size}px`,
transform: `translateY(${virtualItem.start}px)`,
}}
>
{items[virtualItem.index].name}
</div>
))}
</div>
</div>
);
}适用场景:
- 包含100+项的列表
- 包含大量行的表格
- 无限滚动
- 聊天消息或信息流
Advanced Patterns
高级模式
1. Context Optimization
1. Context优化
Split contexts to prevent unnecessary re-renders:
tsx
// ❌ Everything re-renders when anything changes
const AppContext = createContext({
user: null,
theme: "light",
settings: {},
});
// ✅ Separate contexts for independent data
const UserContext = createContext(null);
const ThemeContext = createContext("light");
const SettingsContext = createContext({});
function App() {
const [user, setUser] = useState(null);
const [theme, setTheme] = useState("light");
const [settings, setSettings] = useState({});
return (
<UserContext.Provider value={user}>
<ThemeContext.Provider value={theme}>
<SettingsContext.Provider value={settings}>
<AppContent />
</SettingsContext.Provider>
</ThemeContext.Provider>
</UserContext.Provider>
);
}拆分Context以避免不必要的重渲染:
tsx
// ❌ 任何值变更时所有组件都会重渲染
const AppContext = createContext({
user: null,
theme: "light",
settings: {},
});
// ✅ 为独立数据拆分Context
const UserContext = createContext(null);
const ThemeContext = createContext("light");
const SettingsContext = createContext({});
function App() {
const [user, setUser] = useState(null);
const [theme, setTheme] = useState("light");
const [settings, setSettings] = useState({});
return (
<UserContext.Provider value={user}>
<ThemeContext.Provider value={theme}>
<SettingsContext.Provider value={settings}>
<AppContent />
</SettingsContext.Provider>
</ThemeContext.Provider>
</UserContext.Provider>
);
}2. Component Composition vs Props
2. 组件组合 vs Props
Use children instead of passing components as props:
tsx
// ❌ Slower prop creates new component
function Parent({ content }) {
const [state, setState] = useState(0);
return <div>{content}</div>;
}
<Parent content={<ExpensiveComponent />} />
// ✅ Faster - children don't re-render
function Parent({ children }) {
const [state, setState] = useState(0);
return <div>{children}</div>;
}
<Parent>
<ExpensiveComponent />
</Parent>使用children而非传递组件作为props:
tsx
// ❌ 较慢——props会创建新组件
function Parent({ content }) {
const [state, setState] = useState(0);
return <div>{content}</div>;
}
<Parent content={<ExpensiveComponent />} />
// ✅ 更快——children不会重渲染
function Parent({ children }) {
const [state, setState] = useState(0);
return <div>{children}</div>;
}
<Parent>
<ExpensiveComponent />
</Parent>3. State Colocation
3. 状态就近原则
Move state closer to where it's used:
tsx
// ❌ Top-level state causes full tree re-render
function App() {
const [color, setColor] = useState("blue");
return (
<div>
<ColorPicker color={color} onChange={setColor} />
<ExpensiveComponent />
<AnotherExpensiveComponent />
</div>
);
}
// ✅ Isolated state only affects relevant components
function App() {
return (
<div>
<ColorPickerSection />
<ExpensiveComponent />
<AnotherExpensiveComponent />
</div>
);
}
function ColorPickerSection() {
const [color, setColor] = useState("blue");
return <ColorPicker color={color} onChange={setColor} />;
}将状态移至使用它的组件附近:
tsx
// ❌ 顶层状态导致整个组件树重渲染
function App() {
const [color, setColor] = useState("blue");
return (
<div>
<ColorPicker color={color} onChange={setColor} />
<ExpensiveComponent />
<AnotherExpensiveComponent />
</div>
);
}
// ✅ 隔离状态仅影响相关组件
function App() {
return (
<div>
<ColorPickerSection />
<ExpensiveComponent />
<AnotherExpensiveComponent />
</div>
);
}
function ColorPickerSection() {
const [color, setColor] = useState("blue");
return <ColorPicker color={color} onChange={setColor} />;
}Profiling Performance
性能分析
1. React DevTools Profiler
1. React DevTools性能分析器
tsx
// Add Profiler to measure render times
import { Profiler } from "react";
function App() {
return (
<Profiler id="App" onRender={onRenderCallback}>
<AppContent />
</Profiler>
);
}
function onRenderCallback(
id,
phase,
actualDuration,
baseDuration,
startTime,
commitTime
) {
console.log(`${id} (${phase}) took ${actualDuration}ms`);
}tsx
// 添加Profiler测量渲染时间
import { Profiler } from "react";
function App() {
return (
<Profiler id="App" onRender={onRenderCallback}>
<AppContent />
</Profiler>
);
}
function onRenderCallback(
id,
phase,
actualDuration,
baseDuration,
startTime,
commitTime
) {
console.log(`${id}(${phase})耗时 ${actualDuration}ms`);
}2. Performance Timeline
2. 性能时间线
tsx
// Mark performance in browser DevTools
function expensiveOperation() {
performance.mark("expensive-start");
// ... expensive work ...
performance.mark("expensive-end");
performance.measure(
"expensive-operation",
"expensive-start",
"expensive-end"
);
}tsx
// 在浏览器DevTools中标记性能
function expensiveOperation() {
performance.mark("expensive-start");
// ... 耗时操作 ...
performance.mark("expensive-end");
performance.measure(
"expensive-operation",
"expensive-start",
"expensive-end"
);
}Common Issues
常见问题
Issue 1: React.memo Not Working
问题1:React.memo未生效
Symptoms: Memoized component still re-renders
Cause: Props include objects/arrays/functions that change reference
Solution: Memoize prop values
tsx
// ❌ New object every render
<MemoizedChild config={{ theme: "dark" }} />
// ✅ Stable reference
const config = useMemo(() => ({ theme: "dark" }), []);
<MemoizedChild config={config} />
// ✅ Or use individual props
<MemoizedChild theme="dark" />症状:已缓存的组件仍在重渲染
原因:Props包含引用变更的对象/数组/函数
解决方案:缓存prop值
tsx
// ❌ 每次渲染创建新对象
<MemoizedChild config={{ theme: "dark" }} />
// ✅ 稳定引用
const config = useMemo(() => ({ theme: "dark" }), []);
<MemoizedChild config={config} />
// ✅ 或者使用单独的props
<MemoizedChild theme="dark" />Issue 2: Over-Optimization
问题2:过度优化
Symptoms: Code is complex but not faster
Cause: Memoization overhead exceeds benefit
Solution: Remove unnecessary optimization
tsx
// ❌ Overkill - simple operations are fast
const result = useMemo(() => a + b, [a, b]);
// ✅ Just calculate it
const result = a + b;症状:代码复杂但并未变快
原因:缓存的开销超过了收益
解决方案:移除不必要的优化
tsx
// ❌ 小题大做——简单操作速度足够快
const result = useMemo(() => a + b, [a, b]);
// ✅ 直接计算即可
const result = a + b;Issue 3: Stale Closures
问题3:过时闭包
Symptoms: useCallback uses old values
Cause: Missing dependencies
Solution: Add all dependencies
tsx
// ❌ Uses stale 'count'
const handleClick = useCallback(() => {
console.log(count);
}, []);
// ✅ Always current
const handleClick = useCallback(() => {
console.log(count);
}, [count]);症状:useCallback使用旧值
原因:缺少依赖项
解决方案:添加所有依赖项
tsx
// ❌ 使用过时的'count'
const handleClick = useCallback(() => {
console.log(count);
}, []);
// ✅ 始终使用最新值
const handleClick = useCallback(() => {
console.log(count);
}, [count]);Best Practices
最佳实践
- Profile before optimizing (use React DevTools)
- Start with proper state colocation
- Use React.memo for expensive components with stable props
- Use useMemo for expensive calculations, not simple ones
- Use useCallback for functions passed to memoized children
- Split large components into smaller ones
- Use code splitting for large, conditionally rendered components
- Virtualize long lists (100+ items)
- Split contexts to reduce re-render scope
- Measure impact - ensure optimization actually helps
- 先分析再优化(使用React DevTools)
- 从合理的状态就近原则开始
- 为props稳定的昂贵组件使用React.memo
- 为昂贵计算使用useMemo,而非简单计算
- 为传递给已缓存子组件的函数使用useCallback
- 将大型组件拆分为更小的组件
- 为大型、条件渲染的组件使用代码分割
- 对长列表(100+项)进行虚拟化
- 拆分Context以缩小重渲染范围
- 衡量影响——确保优化确实有效
Anti-Patterns
反模式
Things to avoid:
- ❌ Memoizing everything by default
- ❌ Premature optimization without measuring
- ❌ Using useMemo for simple calculations
- ❌ Forgetting dependencies in hooks
- ❌ Creating new objects/arrays in render
- ❌ Deeply nested component trees
- ❌ Massive components with too many responsibilities
- ❌ Global state for local concerns
需要避免的情况:
- ❌ 默认缓存所有内容
- ❌ 未进行测量就过早优化
- ❌ 为简单计算使用useMemo
- ❌ 忘记钩子中的依赖项
- ❌ 在渲染中创建新对象/数组
- ❌ 深度嵌套的组件树
- ❌ 承担过多职责的巨型组件
- ❌ 为局部状态使用全局状态
Performance Checklist
性能检查清单
When debugging slow components:
- Identify - Use Profiler to find slow components
- Measure - Record baseline performance
- Analyze - Check why component renders
- Optimize - Apply appropriate technique
- Verify - Measure improvement
- Document - Note why optimization was needed
调试缓慢组件时:
- 识别 - 使用性能分析器找到缓慢的组件
- 测量 - 记录基准性能
- 分析 - 检查组件重渲染的原因
- 优化 - 应用合适的技巧
- 验证 - 测量优化效果
- 记录 - 说明优化的原因
References
参考资料
- React Optimization Documentation
- React.memo API
- useMemo Hook
- useCallback Hook
- Code Splitting Guide
- React DevTools Profiler
- render-performance skill
- bundle-optimizer skill
- React优化文档
- React.memo API
- useMemo钩子
- useCallback钩子
- 代码分割指南
- React DevTools性能分析器
- 渲染性能技能
- 包优化器技能