"这页面怎么越来越卡了?"上周四的晚上,我正准备下班,产品经理急匆匆地找到我。我们的后台管理系统在使用了半年后,性能开始明显下降 - 页面切换变慢,表单操作有延迟,甚至连简单的下拉框都开始卡顿。作为项目的主要开发者,我感到既困扰又内疚。😅
这个周末,我决定彻底解决这个问题。经过两天的深入排查和优化,我们不仅解决了性能问题,还建立了一套可持续的性能优化方案。今天,我想和大家分享这个过程中的实战经验。
问题的发现
说实话,性能问题不是一天形成的。回顾代码,我发现了几个主要的性能隐患:
// 1. 过度使用 Context 导致不必要的重渲染
const AppContext = React.createContext({})
function App() {
const [state, setState] = useState({
user: null,
theme: 'light',
notifications: []
// ... 更多全局状态
})
return (
<AppContext.Provider value={state}>
<Layout>
<Sidebar />
<Content />
</Layout>
</AppContext.Provider>
)
}
// 2. 大量计算没有缓存
function ProductList({ products }) {
// 每次渲染都会重新计算
const sortedProducts = products
.map(p => ({
...p,
price: calculateDiscount(p.price, p.discount)
}))
.sort((a, b) => b.price - a.price)
return (
<div>
{sortedProducts.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
)
}
优化之路
状态管理的重构
首先,我们需要重新思考状态管理的方式。与其把所有状态都放在全局 Context 中,不如根据数据的用途来划分:
// 把状态按照功能域拆分
const ThemeContext = React.createContext('light')
const UserContext = React.createContext(null)
function App() {
const [theme, setTheme] = useState('light')
const [user, setUser] = useState(null)
return (
<ThemeContext.Provider value={theme}>
<UserContext.Provider value={user}>
<Layout>
{/* 只有依赖主题的组件才会在主题变化时重渲染 */}
<Sidebar />
<Content />
</Layout>
</UserContext.Provider>
</ThemeContext.Provider>
)
}
// 使用 useMemo 缓存计算结果
function ProductList({ products }) {
const sortedProducts = useMemo(() => {
return products
.map(p => ({
...p,
price: calculateDiscount(p.price, p.discount)
}))
.sort((a, b) => b.price - a.price)
}, [products]) // 只在 products 变化时重新计算
return (
<div>
{sortedProducts.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
)
}
虚拟列表的实现
对于长列表,我们实现了虚拟滚动。这个优化效果特别明显,因为我们的表格动辄上千行数据:
function VirtualList({ items, rowHeight, visibleRows }) {
const [scrollTop, setScrollTop] = useState(0)
const containerRef = useRef(null)
// 计算可见区域的起���和结束索引
const startIndex = Math.floor(scrollTop / rowHeight)
const endIndex = Math.min(startIndex + visibleRows, items.length)
// 只渲染可见区域的数据
const visibleItems = useMemo(() => {
return items.slice(startIndex, endIndex).map((item, index) => ({
...item,
style: {
position: 'absolute',
top: (startIndex + index) * rowHeight,
height: rowHeight
}
}))
}, [items, startIndex, endIndex, rowHeight])
const handleScroll = useCallback(e => {
setScrollTop(e.target.scrollTop)
}, [])
return (
<div ref={containerRef} style={{ height: visibleRows * rowHeight, overflow: 'auto' }} onScroll={handleScroll}>
<div style={{ height: items.length * rowHeight, position: 'relative' }}>
{visibleItems.map(item => (
<div key={item.id} style={item.style}>
{item.content}
</div>
))}
</div>
</div>
)
}
组件懒加载
我们还发现很多组件其实不需要在首次加载时就渲染。通过 React.lazy 和 Suspense,我们实现了按需加载:
const DataAnalytics = React.lazy(() => import('./DataAnalytics'))
const UserSettings = React.lazy(() => import('./UserSettings'))
function Dashboard() {
const [activeTab, setActiveTab] = useState('overview')
return (
<div>
<TabList onChange={setActiveTab} />
<Suspense fallback={<LoadingSpinner />}>
{activeTab === 'analytics' && <DataAnalytics />}
{activeTab === 'settings' && <UserSettings />}
</Suspense>
</div>
)
}
性能监控的建立
优化不是一次性的工作。我们建立了性能监控系统,持续跟踪关键指标:
// hooks/usePerformanceMonitor.ts
function usePerformanceMonitor(componentName: string) {
useEffect(() => {
const startTime = performance.now()
return () => {
const duration = performance.now() - startTime
// 发送性能数据到监控系统
if (duration > 16) {
// 超过一帧的时间
reportPerformanceIssue({
component: componentName,
duration,
timestamp: Date.now()
})
}
}
}, [componentName])
}
收获与思考
这次优化经历让我深刻认识到:性能优化不仅仅是代码层面的事情,更需要从架构层面去思考。比如:
- 状态管理要精细化,避免过度集中
- 计算密集的操作要善用缓存
- 大数据渲染要考虑分片或虚拟化
- 组件加载要按需进行
最重要的是,我们建立了性能监控机制,这让我们能够及时发现和解决问题,而不是等到用户抱怨才开始着手处理。
写在最后
性能优化是一个持续的过程,没有一劳永逸的解决方案。关键是要建立起性能意识,在日常开发中就注意代码质量和性能影响。正如那句老话说的:"性能是设计出来的,不是优化出来的。"
有什么问题欢迎在评论区讨论,我们一起学习进步!
如果觉得有帮助,别忘了点赞关注,我会继续分享更多 React 开发实战经验~
原文:https://juejin.cn/post/7445926398399741992