React.memo
用于防止不必要的重新渲染。
基本用法
const ExpensiveComponent = React.memo(function ExpensiveComponent({ name }) {
console.log('渲染 ExpensiveComponent');
return <div>Hello {name}</div>;
});
function Parent() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>点击 {count}</button>
<ExpensiveComponent name="张三" />
</div>
);
}
自定义比较函数
function areEqual(prevProps, nextProps) {
// 只在 name 改变时重新渲染
return prevProps.name === nextProps.name;
}
const MemoizedComponent = React.memo(Component, areEqual);
useMemo
缓存计算结果,避免重复计算。
使用场景
function ProductList({ products, filter }) {
const filteredProducts = useMemo(() => {
console.log('过滤产品列表...');
return products.filter(p => p.category === filter);
}, [products, filter]);
return (
<ul>
{filteredProducts.map(product => (
<li key={product.id}>{product.name}</li>
))}
</ul>
);
}
不需要使用的情况
// ❌ 不需要 useMemo(计算很简单)
function Component({ a, b }) {
const result = useMemo(() => a + b, [a, b]);
return <div>{result}</div>;
}
// ✅ 直接计算即可
function Component({ a, b }) {
const result = a + b;
return <div>{result}</div>;
}
useCallback
缓存函数,避免子组件不必要的重新渲染。
基本用法
function Parent() {
const [count, setCount] = useState(0);
// 使用 useCallback
const handleClick = useCallback(() => {
setCount(c => c + 1);
}, []); // 空依赖数组,函数只创建一次
return <ChildComponent onClick={handleClick} />;
}
// 子组件使用 React.memo
const ChildComponent = React.memo(function ChildComponent({ onClick }) {
console.log('渲染 ChildComponent');
return <button onClick={onClick}>点击</button>;
});
依赖其他 state
function TodoList({ todos }) {
const [filter, setFilter] = useState('all');
const filteredTodos = useMemo(() => {
return todos.filter(todo => {
if (filter === 'active') return !todo.completed;
if (filter === 'completed') return todo.completed;
return true;
});
}, [todos, filter]);
const handleToggle = useCallback((id) => {
setTodos(prev => prev.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
));
}, [setTodos]);
return (
<div>
<button onClick={() => setFilter('all')}>全部</button>
<button onClick={() => setFilter('active')}>未完成</button>
<button onClick={() => setFilter('completed')}>已完成</button>
<TodoList items={filteredTodos} onToggle={handleToggle} />
</div>
);
}
代码分割和懒加载
React.lazy
import { lazy, Suspense } from 'react';
const HeavyComponent = lazy(() => import('./HeavyComponent'));
function App() {
return (
<Suspense fallback={<div>加载中...</div>}>
<HeavyComponent />
</Suspense>
);
}
基于路由的代码分割
import { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About'));
function App() {
return (
<BrowserRouter>
<nav>
<Link to="/">首页</Link>
<Link to="/about">关于</Link>
</nav>
<Suspense fallback={<div>加载中...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}
虚拟化长列表
使用 react-window 渲染大量数据。
import { FixedSizeList } from 'react-window';
function Row({ index, style }) {
return (
<div style={style}>
第 {index} 行
</div>
);
}
function VirtualizedList({ items }) {
return (
<FixedSizeList
height={400}
width={300}
itemCount={items.length}
itemSize={35}
>
{Row}
</FixedSizeList>
);
}
💡 最佳实践
- 测量优化前先测量:使用 React DevTools Profiler
- 不要过度优化:只有遇到性能问题才优化
- 保持组件简单:小组件更容易优化
- 避免不必要的 state:减少渲染次数
🎯 练习
- 使用 React.memo 优化一个列表组件
- 使用 useMemo 缓存复杂的计算
- 实现路由级别的代码分割
下一节我们将学习 React 项目实战!