React 性能优化实践
实际项目中常用的 React 性能优化方法
问题
在实际工作中,你对 React 做过哪些优化?
解答
1. 使用 React.memo 避免不必要的重渲染
// 子组件用 React.memo 包裹,只有 props 变化时才重渲染
const UserCard = React.memo(function UserCard({ name, avatar }) {
console.log('UserCard render');
return (
<div className="user-card">
<img src={avatar} alt={name} />
<span>{name}</span>
</div>
);
});
// 父组件
function UserList() {
const [count, setCount] = useState(0);
const user = { name: 'John', avatar: '/avatar.png' };
return (
<div>
{/* count 变化不会导致 UserCard 重渲染 */}
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
<UserCard name={user.name} avatar={user.avatar} />
</div>
);
}
2. useMemo 缓存计算结果
function ProductList({ products, filter }) {
// 只有 products 或 filter 变化时才重新计算
const filteredProducts = useMemo(() => {
console.log('filtering products...');
return products.filter(p => p.category === filter);
}, [products, filter]);
return (
<ul>
{filteredProducts.map(p => (
<li key={p.id}>{p.name}</li>
))}
</ul>
);
}
3. useCallback 缓存函数引用
function SearchBox({ onSearch }) {
const [keyword, setKeyword] = useState('');
// 缓存函数,避免子组件不必要的重渲染
const handleSearch = useCallback(() => {
onSearch(keyword);
}, [keyword, onSearch]);
return (
<div>
<input value={keyword} onChange={e => setKeyword(e.target.value)} />
<SearchButton onClick={handleSearch} />
</div>
);
}
const SearchButton = React.memo(({ onClick }) => {
console.log('SearchButton render');
return <button onClick={onClick}>搜索</button>;
});
4. 虚拟列表处理大数据
import { FixedSizeList } from 'react-window';
function VirtualList({ items }) {
// 只渲染可视区域内的元素
const Row = ({ index, style }) => (
<div style={style} className="x7o55">
{items[index].name}
</div>
);
return (
<FixedSizeList
height={400} // 容器高度
width="100%"
itemCount={items.length}
itemSize={50} // 每项高度
>
{Row}
</FixedSizeList>
);
}
5. 路由懒加载
import { lazy, Suspense } from 'react';
import { Routes, Route } from 'react-router-dom';
// 懒加载组件,按需加载
const Home = lazy(() => import('./pages/Home'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
);
}
6. 状态下沉,减少渲染范围
// ❌ 不好:整个组件都会因为 hover 状态重渲染
function ProductPage() {
const [isHovered, setIsHovered] = useState(false);
return (
<div>
<ExpensiveHeader />
<div
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
style={{ background: isHovered ? '#eee' : '#fff' }}
>
Hover me
</div>
<ExpensiveFooter />
</div>
);
}
// ✅ 好:把状态下沉到需要的组件
function ProductPage() {
return (
<div>
<ExpensiveHeader />
<HoverBox />
<ExpensiveFooter />
</div>
);
}
function HoverBox() {
const [isHovered, setIsHovered] = useState(false);
return (
<div
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
style={{ background: isHovered ? '#eee' : '#fff' }}
>
Hover me
</div>
);
}
7. 正确使用 key
// ❌ 不好:用 index 作为 key,列表变化时会导致不必要的重渲染
{items.map((item, index) => (
<ListItem key={index} data={item} />
))}
// ✅ 好:用唯一标识作为 key
{items.map(item => (
<ListItem key={item.id} data={item} />
))}
关键点
- React.memo 包裹子组件,配合 useCallback 缓存传递的函数,避免子组件不必要的重渲染
- useMemo 缓存复杂计算结果,避免每次渲染都重新计算
- 大列表使用 react-window 或 react-virtualized 实现虚拟滚动
- 路由和大组件使用 lazy + Suspense 实现按需加载
- 状态下沉:把频繁变化的状态放到最小的组件中,减少重渲染范围
目录