React性能优化实战指南
•编程技术
React性能优化实战指南
React应用性能优化是前端开发中的重要课题。本文将从多个维度介绍React性能优化的实用技巧。
渲染优化
1. 使用React.memo
import React, { memo } from 'react';
// 优化前
function UserCard({ user, onUpdate }) {
return (
<div className="user-card">
<h3>{user.name}</h3>
<p>{user.email}</p>
<button onClick={() => onUpdate(user.id)}>更新</button>
</div>
);
}
// 优化后
const UserCard = memo(function UserCard({ user, onUpdate }) {
return (
<div className="user-card">
<h3>{user.name}</h3>
<p>{user.email}</p>
<button onClick={() => onUpdate(user.id)}>更新</button>
</div>
);
}, (prevProps, nextProps) => {
// 自定义比较函数
return prevProps.user.id === nextProps.user.id;
});
2. 使用useMemo和useCallback
import React, { useMemo, useCallback } from 'react';
function DataTable({ data, filters }) {
// 缓存过滤后的数据
const filteredData = useMemo(() => {
return data.filter(item => {
return filters.category ? item.category === filters.category : true;
});
}, [data, filters.category]);
// 缓存回调函数
const handleSort = useCallback((column) => {
// 排序逻辑
}, []);
// 缓存计算属性
const statistics = useMemo(() => {
return {
total: filteredData.length,
average: filteredData.reduce((sum, item) => sum + item.value, 0) / filteredData.length
};
}, [filteredData]);
return (
<div>
<Statistics stats={statistics} />
<Table data={filteredData} onSort={handleSort} />
</div>
);
}
3. 虚拟列表
import { useState, useRef, useEffect } from 'react';
function VirtualList({ items, itemHeight, containerHeight }) {
const [scrollTop, setScrollTop] = useState(0);
const containerRef = useRef(null);
// 计算可见区域
const startIndex = Math.floor(scrollTop / itemHeight);
const visibleCount = Math.ceil(containerHeight / itemHeight);
const endIndex = Math.min(startIndex + visibleCount + 1, items.length);
// 可见的数据项
const visibleItems = items.slice(startIndex, endIndex);
// 总高度
const totalHeight = items.length * itemHeight;
// 偏移量
const offsetY = startIndex * itemHeight;
const handleScroll = (e) => {
setScrollTop(e.target.scrollTop);
};
return (
<div
ref={containerRef}
style={{ height: containerHeight, overflow: 'auto' }}
onScroll={handleScroll}
>
<div style={{ height: totalHeight, position: 'relative' }}>
<div style={{ transform: `translateY(${offsetY}px)` }}>
{visibleItems.map((item, index) => (
<div
key={item.id}
style={{ height: itemHeight }}
>
{item.content}
</div>
))}
</div>
</div>
</div>
);
}
状态管理优化
1. 状态拆分
// 优化前:所有状态在一个对象中
function UserProfile() {
const [state, setState] = useState({
user: null,
posts: [],
comments: [],
loading: false,
error: null
});
// 更新用户会导致整个组件重新渲染
const updateUser = (user) => {
setState(prev => ({ ...prev, user }));
};
}
// 优化后:状态拆分
function UserProfile() {
const [user, setUser] = useState(null);
const [posts, setPosts] = useState([]);
const [comments, setComments] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
}
2. 使用Context优化
import React, { createContext, useContext, useReducer, memo } from 'react';
// 创建Context
const UserContext = createContext(null);
// Reducer
function userReducer(state, action) {
switch (action.type) {
case 'SET_USER':
return { ...state, user: action.payload };
case 'UPDATE_PROFILE':
return { ...state, profile: { ...state.profile, ...action.payload } };
default:
return state;
}
}
// Provider
function UserProvider({ children }) {
const [state, dispatch] = useReducer(userReducer, {
user: null,
profile: {}
});
const value = useMemo(() => ({ state, dispatch }), [state, dispatch]);
return (
<UserContext.Provider value={value}>
{children}
</UserContext.Provider>
);
}
// 自定义Hook
function useUser() {
const context = useContext(UserContext);
if (!context) {
throw new Error('useUser must be used within UserProvider');
}
return context;
}
// 使用示例
const UserProfile = memo(function UserProfile() {
const { state } = useUser();
return <div>{state.user?.name}</div>;
});
const UserSettings = memo(function UserSettings() {
const { dispatch } = useUser();
const updateProfile = (profile) => {
dispatch({ type: 'UPDATE_PROFILE', payload: profile });
};
return <button onClick={() => updateProfile({ theme: 'dark' })}>切换主题</button>;
});
代码分割
1. React.lazy和Suspense
import React, { lazy, Suspense } from 'react';
// 懒加载组件
const Dashboard = lazy(() => import('./Dashboard'));
const UserProfile = lazy(() => import('./UserProfile'));
const Settings = lazy(() => import('./Settings'));
// 加载状态组件
function LoadingSpinner() {
return <div className="spinner">加载中...</div>;
}
function App() {
return (
<Router>
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/profile" element={<UserProfile />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
</Router>
);
}
2. 基于路由的代码分割
import { lazy } from 'react';
// 使用webpack的魔法注释
const AdminPanel = lazy(() => import(
/* webpackChunkName: "admin" */
/* webpackPrefetch: true */
'./AdminPanel'
));
const UserDashboard = lazy(() => import(
/* webpackChunkName: "dashboard" */
'./UserDashboard'
));
const Analytics = lazy(() => import(
/* webpackChunkName: "analytics" */
'./Analytics'
));
图片优化
1. 懒加载图片
import { useState, useRef, useEffect } from 'react';
function LazyImage({ src, alt, placeholder }) {
const [isLoaded, setIsLoaded] = useState(false);
const [isInView, setIsInView] = useState(false);
const imgRef = useRef(null);
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setIsInView(true);
observer.disconnect();
}
},
{ rootMargin: '50px' }
);
if (imgRef.current) {
observer.observe(imgRef.current);
}
return () => observer.disconnect();
}, []);
return (
<div ref={imgRef} className="lazy-image-container">
{!isLoaded && placeholder}
{isInView && (
<img
src={src}
alt={alt}
onLoad={() => setIsLoaded(true)}
style={{ opacity: isLoaded ? 1 : 0, transition: 'opacity 0.3s' }}
/>
)}
</div>
);
}
2. 响应式图片
function ResponsiveImage({ srcSet, sizes, alt }) {
return (
<picture>
{srcSet.map((src, index) => (
<source
key={index}
media={src.media}
srcSet={src.url}
type={src.type}
/>
))}
<img
src={srcSet[0].url}
alt={alt}
loading="lazy"
decoding="async"
/>
</picture>
);
}
// 使用示例
<ResponsiveImage
srcSet={[
{ url: 'image-small.jpg', media: '(max-width: 600px)', type: 'image/jpeg' },
{ url: 'image-medium.jpg', media: '(max-width: 1200px)', type: 'image/jpeg' },
{ url: 'image-large.jpg', media: '(min-width: 1201px)', type: 'image/jpeg' }
]}
alt="响应式图片"
/>
Web Workers
// worker.js
self.addEventListener('message', (e) => {
const { type, data } = e.data;
if (type === 'HEAVY_CALCULATION') {
const result = performHeavyCalculation(data);
self.postMessage({ type: 'RESULT', result });
}
});
function performHeavyCalculation(data) {
// 耗时计算
let result = 0;
for (let i = 0; i < data.length; i++) {
result += complexOperation(data[i]);
}
return result;
}
// 使用Worker
import { useEffect, useRef, useState } from 'react';
function useWorker() {
const workerRef = useRef(null);
const [result, setResult] = useState(null);
useEffect(() => {
workerRef.current = new Worker(new URL('./worker.js', import.meta.url));
workerRef.current.addEventListener('message', (e) => {
if (e.data.type === 'RESULT') {
setResult(e.data.result);
}
});
return () => {
workerRef.current?.terminate();
};
}, []);
const postMessage = (data) => {
workerRef.current?.postMessage(data);
};
return { result, postMessage };
}
性能监控
import { Profiler } from 'react';
function onRenderCallback(
id, // 组件的标识
phase, // 渲染阶段:"mount" | "update"
actualDuration, // 实际渲染耗时
baseDuration, // 预估渲染耗时
startTime, // 开始渲染时间
commitTime // 提交时间
) {
console.log('Component:', id);
console.log('Phase:', phase);
console.log('Actual Duration:', actualDuration);
console.log('Base Duration:', baseDuration);
// 发送到性能监控服务
if (actualDuration > 16) { // 超过一帧的时间
reportSlowRender(id, actualDuration);
}
}
function App() {
return (
<Profiler id="App" onRender={onRenderCallback}>
<Header />
<Main />
<Footer />
</Profiler>
);
}
总结
React性能优化是一个持续的过程,需要根据实际场景选择合适的优化策略:
- 渲染优化:使用memo、useMemo、useCallback避免不必要的渲染
- 状态管理:合理拆分状态,使用Context或状态管理库
- 代码分割:使用React.lazy和动态导入减少初始加载时间
- 资源优化:图片懒加载、响应式图片、资源压缩
- 性能监控:使用Profiler和性能API监控应用性能
记住,过早优化是万恶之源。在优化前,先使用React DevTools Profiler找出真正的性能瓶颈。