作为 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 => (
<ProductItem
key={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 (
<div
ref={containerRef}
style={{ height: windowHeight, overflow: 'auto' }}
onScroll={handleScroll}
>
<div style={{ height: totalHeight, position: 'relative' }}>
{visibleItems.map(item => (
<div
key={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 的测试策略,帮助你构建可靠的应用。
如果觉得这篇文章对你有帮助,别忘了点个赞 👍