作为 Vue 开发者,在迁移到 React 开发时,性能优化的思路和方法会有所不同。本文将从 Vue 开发者熟悉的角度出发,详细介绍 React 中的性能优化策略。
渲染优化对比
Vue 的响应式系统
Vue 通过响应式系统自动追踪依赖,只有在数据真正变化时才会触发重渲染
<template><div><h1>{{ title }}</h1><p>{{ description }}</p><!-- 只有 count 变化时才会重渲染 --><div>点击次数:{{ count }}</div><button @click="increment">+1</button></div>
</template><script>
export default {data() {return {title: '标题',description: '描述',count: 0}},methods: {increment() {this.count++}}
}
</script>
React 的渲染机制
React 默认采用自上而下的渲染策略,父组件更新会触发子组件重渲染:
function App() {const [count, setCount] = useState(0);return (<div><h1>标题</h1><p>描述</p>{/* 每次 count 变化,整个组件树都会重新渲染 */}<div>点击次数:{count}</div><button onClick={() => setCount(count + 1)}>+1</button></div>);
}
优化后的版本:
const Title = memo(function Title() {return <h1>标题</h1>;
});const Description = memo(function Description() {return <p>描述</p>;
});const Counter = memo(function Counter({ count, onIncrement }) {return (<><div>点击次数:{count}</div><button onClick={onIncrement}>+1</button></>);
});function App() {const [count, setCount] = useState(0);const increment = useCallback(() => {setCount(c => c + 1);}, []);return (<div><Title /><Description /><Counter count={count} onIncrement={increment} /></div>);
}
组件优化策略
1. 组件拆分与记忆化
// 不好的实践
function ProductList({ products, onSelect }) {return (<div>{products.map(product => (<div key={product.id} onClick={() => onSelect(product)}><img src={product.image} alt={product.name} /><h3>{product.name}</h3><p>{product.price}</p></div>))}</div>);
}// 好的实践
const ProductItem = memo(function ProductItem({ product, onSelect }) {const handleClick = useCallback(() => {onSelect(product);}, [product, onSelect]);return (<div onClick={handleClick}><img src={product.image} alt={product.name} /><h3>{product.name}</h3><p>{product.price}</p></div>);
});function ProductList({ products, onSelect }) {return (<div>{products.map(product => (<ProductItemkey={product.id}product={product}onSelect={onSelect}/>))}</div>);
}
2. 状态管理优化
// 不好的实践
function Dashboard() {const [state, setState] = useState({user: null,products: [],orders: [],settings: {}});// 任何状态更新都会导致整个组件重渲染const updateUser = (user) => {setState(prev => ({ ...prev, user }));};return (<div><UserProfile user={state.user} onUpdate={updateUser} /><ProductList products={state.products} /><OrderList orders={state.orders} /><Settings settings={state.settings} /></div>);
}// 好的实践
function Dashboard() {const [user, setUser] = useState(null);const [products, setProducts] = useState([]);const [orders, setOrders] = useState([]);const [settings, setSettings] = useState({});return (<div><UserProfile user={user} onUpdate={setUser} /><ProductList products={products} /><OrderList orders={orders} /><Settings settings={settings} /></div>);
}
3. 计算属性优化
// 不好的实践
function OrderSummary({ orders }) {// 每次渲染都会重新计算const totalAmount = orders.reduce((sum, order) => sum + order.amount, 0);const completedOrders = orders.filter(order => order.status === 'completed');const pendingOrders = orders.filter(order => order.status === 'pending');return (<div><p>总金额:{totalAmount}</p><p>已完成订单:{completedOrders.length}</p><p>待处理订单:{pendingOrders.length}</p></div>);
}// 好的实践
function OrderSummary({ orders }) {const totalAmount = useMemo(() => {return orders.reduce((sum, order) => sum + order.amount, 0);}, [orders]);const { completedOrders, pendingOrders } = useMemo(() => {return {completedOrders: orders.filter(order => order.status === 'completed'),pendingOrders: orders.filter(order => order.status === 'pending')};}, [orders]);return (<div><p>总金额:{totalAmount}</p><p>已完成订单:{completedOrders.length}</p><p>待处理订单:{pendingOrders.length}</p></div>);
}
列表渲染优化
1. 虚拟列表
function VirtualList({items,itemHeight,windowHeight,overscan = 3
}) {const [scrollTop, setScrollTop] = useState(0);const containerRef = useRef();const visibleCount = Math.ceil(windowHeight / itemHeight);const totalHeight = items.length * itemHeight;const startIndex = Math.max(0, Math.floor(scrollTop / itemHeight) - overscan);const endIndex = Math.min(items.length,Math.ceil((scrollTop + windowHeight) / itemHeight) + overscan);const visibleItems = useMemo(() => {return items.slice(startIndex, endIndex).map((item, index) => ({...item,index: startIndex + index}));}, [items, startIndex, endIndex]);const handleScroll = useCallback((e) => {setScrollTop(e.target.scrollTop);}, []);return (<divref={containerRef}style={{ height: windowHeight, overflow: 'auto' }}onScroll={handleScroll}><div style={{ height: totalHeight, position: 'relative' }}>{visibleItems.map(item => (<divkey={item.id}style={{position: 'absolute',top: item.index * itemHeight,height: itemHeight}}>{item.content}</div>))}</div></div>);
}
2. 无限滚动
function InfiniteList({ fetchItems, itemHeight = 50 }) {const [items, setItems] = useState([]);const [loading, setLoading] = useState(false);const [hasMore, setHasMore] = useState(true);const [page, setPage] = useState(1);const containerRef = useRef();const loadMore = useCallback(async () => {if (loading || !hasMore) return;setLoading(true);try {const newItems = await fetchItems(page);if (newItems.length === 0) {setHasMore(false);} else {setItems(prev => [...prev, ...newItems]);setPage(p => p + 1);}} finally {setLoading(false);}}, [fetchItems, page, loading, hasMore]);useEffect(() => {const container = containerRef.current;if (!container) return;const observer = new IntersectionObserver(entries => {if (entries[0].isIntersecting) {loadMore();}},{ threshold: 0.5 });const sentinel = container.lastElementChild;if (sentinel) {observer.observe(sentinel);}return () => observer.disconnect();}, [loadMore]);return (<div ref={containerRef} style={{ height: '100vh', overflow: 'auto' }}>{items.map(item => (<div key={item.id} style={{ height: itemHeight }}>{item.content}</div>))}{hasMore && (<div style={{ height: itemHeight, textAlign: 'center' }}>{loading ? '加载中...' : '向下滚动加载更多'}</div>)}</div>);
}
数据获取优化
1. 请求缓存
function useQuery(key, fetcher, options = {}) {const cache = useRef(new Map());const [data, setData] = useState(null);const [error, setError] = useState(null);const [loading, setLoading] = useState(true);useEffect(() => {const fetchData = async () => {if (cache.current.has(key) && !options.forceRefetch) {setData(cache.current.get(key));setLoading(false);return;}setLoading(true);try {const result = await fetcher();cache.current.set(key, result);setData(result);setError(null);} catch (err) {setError(err);setData(null);} finally {setLoading(false);}};fetchData();}, [key, fetcher, options.forceRefetch]);return { data, error, loading };
}
2. 请求去重
function useDedupeQuery(key, fetcher) {const pendingRequests = useRef(new Map());const executeQuery = useCallback(async () => {if (pendingRequests.current.has(key)) {return pendingRequests.current.get(key);}const promise = fetcher();pendingRequests.current.set(key, promise);try {const result = await promise;pendingRequests.current.delete(key);return result;} catch (error) {pendingRequests.current.delete(key);throw error;}}, [key, fetcher]);return useQuery(key, executeQuery);
}
代码分割
1. 路由级别分割
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Profile = lazy(() => import('./pages/Profile'));
const Settings = lazy(() => import('./pages/Settings'));function App() {return (<Suspense fallback={<Loading />}><Routes><Route path="/" element={<Dashboard />} /><Route path="/profile" element={<Profile />} /><Route path="/settings" element={<Settings />} /></Routes></Suspense>);
}
2. 组件级别分割
const HeavyChart = lazy(() => import('./components/HeavyChart'));function Dashboard() {const [showChart, setShowChart] = useState(false);return (<div><button onClick={() => setShowChart(true)}>显示图表</button>{showChart && (<Suspense fallback={<Loading />}><HeavyChart /></Suspense>)}</div>);
}
工具和监控
1. 性能分析
import { Profiler } from 'react';function onRenderCallback(id,phase,actualDuration,baseDuration,startTime,commitTime,interactions
) {console.log({id,phase,actualDuration,baseDuration,startTime,commitTime,interactions});
}function App() {return (<Profiler id="App" onRender={onRenderCallback}><div>{/* 应用内容 */}</div></Profiler>);
}
2. 性能监控
function usePerformanceMonitor() {useEffect(() => {const observer = new PerformanceObserver((list) => {for (const entry of list.getEntries()) {if (entry.entryType === 'largest-contentful-paint') {console.log('LCP:', entry.startTime);}if (entry.entryType === 'first-input') {console.log('FID:', entry.processingStart - entry.startTime);}if (entry.entryType === 'layout-shift') {console.log('CLS:', entry.value);}}});observer.observe({entryTypes: ['largest-contentful-paint', 'first-input', 'layout-shift']});return () => observer.disconnect();}, []);
}
最佳实践
渲染优化
- 合理拆分组件
- 使用 memo 避免不必要的重渲染
- 优化计算属性
- 合理使用 Context
状态管理
- 状态粒度适中
- 避免冗余状态
- 使用不可变数据
- 合理使用状态管理库
数据处理
- 实现请求缓存
- 避免重复请求
- 优化大数据渲染
- 使用虚拟列表
代码组织
- 合理代码分割
- 按需加载
- 预加载关键资源
- 优化打包体积
小结
React 性能优化的特点:
- 组件级别优化
- 状态管理优化
- 渲染机制优化
- 资源加载优化
从 Vue 到 React 的转变:
- 理解渲染机制差异
- 掌握优化工具
- 建立性能意识
- 实践优化策略
开发建议:
- 先测量后优化
- 避免过早优化
- 关注用户体验
- 持续监控改进
下一篇文章,我们将深入探讨 React 的测试策略,帮助你构建可靠的应用。
如果觉得这篇文章对你有帮助,别忘了点个赞 👍