🚀 引言

在现代Web开发中,用户对应用性能的要求越来越高。一个加载缓慢、交互卡顿的应用很容易失去用户。React作为最流行的前端框架之一,虽然提供了强大的功能,但如果不注意性能优化,很容易出现性能瓶颈。

本文将从多个维度深入探讨React性能优化的策略和技巧,帮助你构建高性能的React应用。

📊 性能优化的核心原则

1. 减少不必要的渲染

React的核心思想是通过虚拟DOM来优化渲染性能,但不当的使用仍然会导致性能问题。

React.memo 的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import React, { memo, useState, useCallback } from 'react';

// 未优化的组件
const ExpensiveComponent = ({ data, onUpdate }) => {
console.log('ExpensiveComponent 重新渲染');

return (
<div>
<h3>{data.title}</h3>
<p>{data.content}</p>
<button onClick={() => onUpdate(data.id)}>更新</button>
</div>
);
};

// 使用 React.memo 优化
const OptimizedComponent = memo(({ data, onUpdate }) => {
console.log('OptimizedComponent 重新渲染');

return (
<div>
<h3>{data.title}</h3>
<p>{data.content}</p>
<button onClick={() => onUpdate(data.id)}>更新</button>
</div>
);
});

// 自定义比较函数
const ComponentWithCustomComparison = memo(({ data, onUpdate }) => {
return (
<div>
<h3>{data.title}</h3>
<p>{data.content}</p>
<button onClick={() => onUpdate(data.id)}>更新</button>
</div>
);
}, (prevProps, nextProps) => {
// 只有当 data.title 或 data.content 改变时才重新渲染
return prevProps.data.title === nextProps.data.title &&
prevProps.data.content === nextProps.data.content;
});

// 父组件
const ParentComponent = () => {
const [items, setItems] = useState([
{ id: 1, title: '标题1', content: '内容1' },
{ id: 2, title: '标题2', content: '内容2' }
]);
const [counter, setCounter] = useState(0);

// 使用 useCallback 优化回调函数
const handleUpdate = useCallback((id) => {
setItems(prevItems =>
prevItems.map(item =>
item.id === id
? { ...item, content: `更新的内容 ${Date.now()}` }
: item
)
);
}, []);

return (
<div>
<button onClick={() => setCounter(c => c + 1)}>
计数器: {counter}
</button>

{items.map(item => (
<OptimizedComponent
key={item.id}
data={item}
onUpdate={handleUpdate}
/>
))}
</div>
);
};

useMemo 和 useCallback 的正确使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import React, { useState, useMemo, useCallback } from 'react';

const DataProcessor = ({ rawData, filters }) => {
const [sortOrder, setSortOrder] = useState('asc');

// 使用 useMemo 缓存昂贵的计算
const processedData = useMemo(() => {
console.log('处理数据...');

// 模拟复杂的数据处理
let filtered = rawData.filter(item => {
return filters.every(filter => {
switch (filter.type) {
case 'category':
return item.category === filter.value;
case 'price':
return item.price >= filter.min && item.price <= filter.max;
case 'rating':
return item.rating >= filter.value;
default:
return true;
}
});
});

// 排序
filtered.sort((a, b) => {
const multiplier = sortOrder === 'asc' ? 1 : -1;
return (a.name.localeCompare(b.name)) * multiplier;
});

return filtered;
}, [rawData, filters, sortOrder]);

// 使用 useCallback 缓存事件处理函数
const handleSortChange = useCallback((newSortOrder) => {
setSortOrder(newSortOrder);
}, []);

const handleItemClick = useCallback((itemId) => {
console.log('点击项目:', itemId);
// 处理点击事件
}, []);

return (
<div>
<div>
<button onClick={() => handleSortChange('asc')}>升序</button>
<button onClick={() => handleSortChange('desc')}>降序</button>
</div>

<div>
{processedData.map(item => (
<ItemCard
key={item.id}
item={item}
onClick={handleItemClick}
/>
))}
</div>
</div>
);
};

const ItemCard = memo(({ item, onClick }) => {
return (
<div
className="item-card"
onClick={() => onClick(item.id)}
>
<h3>{item.name}</h3>
<p>价格: ¥{item.price}</p>
<p>评分: {item.rating}/5</p>
</div>
);
});

2. 虚拟化长列表

对于包含大量数据的列表,虚拟化是提升性能的关键技术。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
import React, { useState, useMemo } from 'react';
import { FixedSizeList as List } from 'react-window';

// 虚拟化列表组件
const VirtualizedList = ({ items, height = 400, itemHeight = 50 }) => {
const [searchTerm, setSearchTerm] = useState('');

// 过滤数据
const filteredItems = useMemo(() => {
if (!searchTerm) return items;
return items.filter(item =>
item.name.toLowerCase().includes(searchTerm.toLowerCase())
);
}, [items, searchTerm]);

// 列表项渲染函数
const Row = ({ index, style }) => {
const item = filteredItems[index];

return (
<div style={style} className="list-item">
<div className="item-content">
<h4>{item.name}</h4>
<p>{item.description}</p>
<span className="item-meta">
{item.category} | {item.date}
</span>
</div>
</div>
);
};

return (
<div className="virtualized-list-container">
<div className="search-container">
<input
type="text"
placeholder="搜索..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="search-input"
/>
</div>

<List
height={height}
itemCount={filteredItems.length}
itemSize={itemHeight}
width="100%"
>
{Row}
</List>
</div>
);
};

// 动态高度的虚拟化列表
import { VariableSizeList as VariableList } from 'react-window';

const DynamicVirtualizedList = ({ items }) => {
const [itemHeights, setItemHeights] = useState({});

// 获取项目高度
const getItemSize = (index) => {
return itemHeights[index] || 80; // 默认高度
};

// 设置项目高度
const setItemHeight = (index, height) => {
setItemHeights(prev => ({
...prev,
[index]: height
}));
};

const Row = ({ index, style }) => {
const item = items[index];
const rowRef = useRef();

useEffect(() => {
if (rowRef.current) {
const height = rowRef.current.getBoundingClientRect().height;
setItemHeight(index, height);
}
}, [index]);

return (
<div style={style}>
<div ref={rowRef} className="dynamic-item">
<h4>{item.title}</h4>
<p>{item.content}</p>
{item.image && (
<img src={item.image} alt={item.title} />
)}
</div>
</div>
);
};

return (
<VariableList
height={600}
itemCount={items.length}
itemSize={getItemSize}
width="100%"
>
{Row}
</VariableList>
);
};

3. 代码分割和懒加载

通过代码分割减少初始包大小,提升首屏加载速度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';

// 懒加载组件
const HomePage = lazy(() => import('./pages/HomePage'));
const AboutPage = lazy(() => import('./pages/AboutPage'));
const ProductPage = lazy(() => import('./pages/ProductPage'));
const UserDashboard = lazy(() => import('./pages/UserDashboard'));

// 加载中组件
const LoadingSpinner = () => (
<div className="loading-container">
<div className="spinner"></div>
<p>加载中...</p>
</div>
);

// 错误边界组件
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}

static getDerivedStateFromError(error) {
return { hasError: true };
}

componentDidCatch(error, errorInfo) {
console.error('组件加载错误:', error, errorInfo);
}

render() {
if (this.state.hasError) {
return (
<div className="error-container">
<h2>页面加载失败</h2>
<button onClick={() => window.location.reload()}>
重新加载
</button>
</div>
);
}

return this.props.children;
}
}

// 主应用组件
const App = () => {
return (
<Router>
<div className="app">
<nav className="navigation">
<Link to="/">首页</Link>
<Link to="/about">关于</Link>
<Link to="/products">产品</Link>
<Link to="/dashboard">控制台</Link>
</nav>

<main className="main-content">
<ErrorBoundary>
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/about" element={<AboutPage />} />
<Route path="/products" element={<ProductPage />} />
<Route path="/dashboard" element={<UserDashboard />} />
</Routes>
</Suspense>
</ErrorBoundary>
</main>
</div>
</Router>
);
};

// 动态导入工具函数
const loadComponent = (componentPath) => {
return lazy(() =>
import(componentPath).catch(error => {
console.error(`组件加载失败: ${componentPath}`, error);
// 返回一个错误组件
return {
default: () => (
<div className="component-error">
<p>组件加载失败,请刷新页面重试</p>
</div>
)
};
})
);
};

🔧 状态管理优化

1. 合理的状态结构设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
import React, { useReducer, useContext, createContext } from 'react';

// 状态管理的最佳实践
const initialState = {
user: {
profile: null,
preferences: {},
isLoading: false,
error: null
},
products: {
items: [],
filters: {
category: '',
priceRange: [0, 1000],
rating: 0
},
pagination: {
page: 1,
pageSize: 20,
total: 0
},
isLoading: false,
error: null
},
cart: {
items: [],
total: 0,
isLoading: false
}
};

// Action 类型
const ActionTypes = {
// User actions
SET_USER_LOADING: 'SET_USER_LOADING',
SET_USER_PROFILE: 'SET_USER_PROFILE',
SET_USER_ERROR: 'SET_USER_ERROR',

// Products actions
SET_PRODUCTS_LOADING: 'SET_PRODUCTS_LOADING',
SET_PRODUCTS: 'SET_PRODUCTS',
SET_PRODUCTS_FILTER: 'SET_PRODUCTS_FILTER',
SET_PRODUCTS_PAGINATION: 'SET_PRODUCTS_PAGINATION',

// Cart actions
ADD_TO_CART: 'ADD_TO_CART',
REMOVE_FROM_CART: 'REMOVE_FROM_CART',
UPDATE_CART_QUANTITY: 'UPDATE_CART_QUANTITY',
CLEAR_CART: 'CLEAR_CART'
};

// Reducer
const appReducer = (state, action) => {
switch (action.type) {
case ActionTypes.SET_USER_LOADING:
return {
...state,
user: {
...state.user,
isLoading: action.payload
}
};

case ActionTypes.SET_USER_PROFILE:
return {
...state,
user: {
...state.user,
profile: action.payload,
isLoading: false,
error: null
}
};

case ActionTypes.SET_PRODUCTS:
return {
...state,
products: {
...state.products,
items: action.payload.items,
pagination: {
...state.products.pagination,
total: action.payload.total
},
isLoading: false,
error: null
}
};

case ActionTypes.SET_PRODUCTS_FILTER:
return {
...state,
products: {
...state.products,
filters: {
...state.products.filters,
...action.payload
},
pagination: {
...state.products.pagination,
page: 1 // 重置页码
}
}
};

case ActionTypes.ADD_TO_CART:
const existingItem = state.cart.items.find(
item => item.id === action.payload.id
);

let updatedItems;
if (existingItem) {
updatedItems = state.cart.items.map(item =>
item.id === action.payload.id
? { ...item, quantity: item.quantity + 1 }
: item
);
} else {
updatedItems = [...state.cart.items, { ...action.payload, quantity: 1 }];
}

const newTotal = updatedItems.reduce(
(sum, item) => sum + (item.price * item.quantity), 0
);

return {
...state,
cart: {
...state.cart,
items: updatedItems,
total: newTotal
}
};

default:
return state;
}
};

// Context
const AppContext = createContext();

// Provider 组件
export const AppProvider = ({ children }) => {
const [state, dispatch] = useReducer(appReducer, initialState);

// Action creators
const actions = {
setUserLoading: (loading) =>
dispatch({ type: ActionTypes.SET_USER_LOADING, payload: loading }),

setUserProfile: (profile) =>
dispatch({ type: ActionTypes.SET_USER_PROFILE, payload: profile }),

setProducts: (products) =>
dispatch({ type: ActionTypes.SET_PRODUCTS, payload: products }),

setProductsFilter: (filters) =>
dispatch({ type: ActionTypes.SET_PRODUCTS_FILTER, payload: filters }),

addToCart: (product) =>
dispatch({ type: ActionTypes.ADD_TO_CART, payload: product })
};

return (
<AppContext.Provider value={{ state, actions }}>
{children}
</AppContext.Provider>
);
};

// 自定义 Hook
export const useAppState = () => {
const context = useContext(AppContext);
if (!context) {
throw new Error('useAppState must be used within AppProvider');
}
return context;
};

2. 选择器模式优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
import { useMemo } from 'react';

// 选择器 Hooks
export const useUserSelector = () => {
const { state } = useAppState();

return useMemo(() => ({
profile: state.user.profile,
isLoading: state.user.isLoading,
error: state.user.error,
isAuthenticated: !!state.user.profile
}), [state.user]);
};

export const useProductsSelector = () => {
const { state } = useAppState();

return useMemo(() => ({
items: state.products.items,
filters: state.products.filters,
pagination: state.products.pagination,
isLoading: state.products.isLoading,
error: state.products.error
}), [state.products]);
};

export const useCartSelector = () => {
const { state } = useAppState();

return useMemo(() => ({
items: state.cart.items,
total: state.cart.total,
itemCount: state.cart.items.reduce((sum, item) => sum + item.quantity, 0),
isEmpty: state.cart.items.length === 0
}), [state.cart]);
};

// 使用选择器的组件
const ProductList = () => {
const { items, filters, isLoading } = useProductsSelector();
const { actions } = useAppState();

// 过滤后的产品列表
const filteredProducts = useMemo(() => {
return items.filter(product => {
if (filters.category && product.category !== filters.category) {
return false;
}
if (product.price < filters.priceRange[0] || product.price > filters.priceRange[1]) {
return false;
}
if (product.rating < filters.rating) {
return false;
}
return true;
});
}, [items, filters]);

if (isLoading) {
return <div>加载中...</div>;
}

return (
<div className="product-list">
{filteredProducts.map(product => (
<ProductCard
key={product.id}
product={product}
onAddToCart={() => actions.addToCart(product)}
/>
))}
</div>
);
};

🎯 渲染优化技巧

1. 避免内联对象和函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// ❌ 不好的做法
const BadComponent = ({ items }) => {
return (
<div>
{items.map(item => (
<ItemComponent
key={item.id}
item={item}
style={{ margin: '10px', padding: '5px' }} // 每次渲染都创建新对象
onClick={() => console.log(item.id)} // 每次渲染都创建新函数
/>
))}
</div>
);
};

// ✅ 好的做法
const itemStyle = { margin: '10px', padding: '5px' }; // 提取到组件外部

const GoodComponent = ({ items }) => {
const handleItemClick = useCallback((itemId) => {
console.log(itemId);
}, []);

return (
<div>
{items.map(item => (
<ItemComponent
key={item.id}
item={item}
style={itemStyle}
onClick={handleItemClick}
/>
))}
</div>
);
};

2. 合理使用 key 属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// ❌ 使用数组索引作为 key
const BadList = ({ items }) => (
<ul>
{items.map((item, index) => (
<li key={index}>{item.name}</li>
))}
</ul>
);

// ✅ 使用稳定的唯一标识符
const GoodList = ({ items }) => (
<ul>
{items.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);

// ✅ 对于复杂的列表项,使用组合 key
const ComplexList = ({ items, category }) => (
<ul>
{items.map(item => (
<ComplexListItem
key={`${category}-${item.id}`}
item={item}
category={category}
/>
))}
</ul>
);

📦 打包和资源优化

1. Webpack 配置优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
// webpack.config.js
const path = require('path');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
const CompressionPlugin = require('compression-webpack-plugin');

module.exports = {
mode: 'production',

// 代码分割配置
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
// 第三方库单独打包
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
priority: 10
},
// React 相关库单独打包
react: {
test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
name: 'react',
chunks: 'all',
priority: 20
},
// 工具库单独打包
utils: {
test: /[\\/]node_modules[\\/](lodash|moment|axios)[\\/]/,
name: 'utils',
chunks: 'all',
priority: 15
},
// 公共代码
common: {
name: 'common',
minChunks: 2,
chunks: 'all',
priority: 5,
reuseExistingChunk: true
}
}
},

// 运行时代码单独提取
runtimeChunk: {
name: 'runtime'
}
},

plugins: [
// 分析打包结果
new BundleAnalyzerPlugin({
analyzerMode: 'static',
openAnalyzer: false,
reportFilename: 'bundle-report.html'
}),

// Gzip 压缩
new CompressionPlugin({
algorithm: 'gzip',
test: /\.(js|css|html|svg)$/,
threshold: 8192,
minRatio: 0.8
})
],

module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env', {
targets: '> 0.25%, not dead',
useBuiltIns: 'usage',
corejs: 3
}],
'@babel/preset-react'
],
plugins: [
// 按需导入
['import', {
libraryName: 'antd',
libraryDirectory: 'es',
style: 'css'
}]
]
}
}
}
]
}
};

2. 图片和资源优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
import React, { useState, useCallback } from 'react';

// 图片懒加载组件
const LazyImage = ({ src, alt, placeholder, className }) => {
const [isLoaded, setIsLoaded] = useState(false);
const [isError, setIsError] = useState(false);

const handleLoad = useCallback(() => {
setIsLoaded(true);
}, []);

const handleError = useCallback(() => {
setIsError(true);
}, []);

return (
<div className={`lazy-image-container ${className}`}>
{!isLoaded && !isError && (
<div className="image-placeholder">
{placeholder || <div className="loading-skeleton" />}
</div>
)}

{isError ? (
<div className="image-error">
<span>图片加载失败</span>
</div>
) : (
<img
src={src}
alt={alt}
onLoad={handleLoad}
onError={handleError}
style={{ display: isLoaded ? 'block' : 'none' }}
loading="lazy" // 原生懒加载
/>
)}
</div>
);
};

// 响应式图片组件
const ResponsiveImage = ({ src, alt, sizes }) => {
// 生成不同尺寸的图片 URL
const generateSrcSet = (baseSrc) => {
const sizes = [320, 640, 960, 1280, 1920];
return sizes.map(size =>
`${baseSrc}?w=${size} ${size}w`
).join(', ');
};

return (
<img
src={src}
srcSet={generateSrcSet(src)}
sizes={sizes || '(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw'}
alt={alt}
loading="lazy"
/>
);
};

// 图片预加载 Hook
const useImagePreloader = (imageUrls) => {
const [loadedImages, setLoadedImages] = useState(new Set());

useEffect(() => {
const preloadImages = async () => {
const promises = imageUrls.map(url => {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => {
setLoadedImages(prev => new Set([...prev, url]));
resolve(url);
};
img.onerror = reject;
img.src = url;
});
});

try {
await Promise.all(promises);
} catch (error) {
console.error('图片预加载失败:', error);
}
};

preloadImages();
}, [imageUrls]);

return loadedImages;
};

🔍 性能监控和分析

1. 性能指标收集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
import React, { useEffect } from 'react';

// 性能监控 Hook
const usePerformanceMonitor = () => {
useEffect(() => {
// Web Vitals 监控
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach((entry) => {
switch (entry.entryType) {
case 'largest-contentful-paint':
console.log('LCP:', entry.startTime);
// 发送到分析服务
sendMetric('LCP', entry.startTime);
break;

case 'first-input':
console.log('FID:', entry.processingStart - entry.startTime);
sendMetric('FID', entry.processingStart - entry.startTime);
break;

case 'layout-shift':
if (!entry.hadRecentInput) {
console.log('CLS:', entry.value);
sendMetric('CLS', entry.value);
}
break;
}
});
});

observer.observe({ entryTypes: ['largest-contentful-paint', 'first-input', 'layout-shift'] });

return () => observer.disconnect();
}, []);

const sendMetric = (name, value) => {
// 发送性能指标到分析服务
if (navigator.sendBeacon) {
navigator.sendBeacon('/api/metrics', JSON.stringify({
name,
value,
timestamp: Date.now(),
url: window.location.href
}));
}
};
};

// React 组件性能分析
const ComponentProfiler = ({ children, id }) => {
const onRenderCallback = (id, phase, actualDuration, baseDuration, startTime, commitTime) => {
console.log('组件性能分析:', {
id,
phase,
actualDuration,
baseDuration,
startTime,
commitTime
});

// 如果渲染时间过长,记录警告
if (actualDuration > 16) { // 超过一帧的时间
console.warn(`组件 ${id} 渲染时间过长: ${actualDuration}ms`);
}
};

return (
<React.Profiler id={id} onRender={onRenderCallback}>
{children}
</React.Profiler>
);
};

// 使用示例
const App = () => {
usePerformanceMonitor();

return (
<div>
<ComponentProfiler id="ProductList">
<ProductList />
</ComponentProfiler>

<ComponentProfiler id="UserDashboard">
<UserDashboard />
</ComponentProfiler>
</div>
);
};

2. 内存泄漏检测和预防

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import React, { useEffect, useRef, useState } from 'react';

// 防止内存泄漏的 Hook
const useSafeAsync = () => {
const isMountedRef = useRef(true);

useEffect(() => {
return () => {
isMountedRef.current = false;
};
}, []);

const safeSetState = (setState) => {
return (...args) => {
if (isMountedRef.current) {
setState(...args);
}
};
};

return { safeSetState };
};

// 安全的异步组件
const SafeAsyncComponent = () => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const { safeSetState } = useSafeAsync();

const safeSetData = safeSetState(setData);
const safeSetLoading = safeSetState(setLoading);

useEffect(() => {
const fetchData = async () => {
safeSetLoading(true);

try {
const response = await fetch('/api/data');
const result = await response.json();
safeSetData(result);
} catch (error) {
console.error('数据获取失败:', error);
} finally {
safeSetLoading(false);
}
};

fetchData();
}, [safeSetData, safeSetLoading]);

return (
<div>
{loading ? <div>加载中...</div> : <div>{JSON.stringify(data)}</div>}
</div>
);
};

// 事件监听器清理
const useEventListener = (eventName, handler, element = window) => {
const savedHandler = useRef();

useEffect(() => {
savedHandler.current = handler;
}, [handler]);

useEffect(() => {
const isSupported = element && element.addEventListener;
if (!isSupported) return;

const eventListener = (event) => savedHandler.current(event);
element.addEventListener(eventName, eventListener);

return () => {
element.removeEventListener(eventName, eventListener);
};
}, [eventName, element]);
};

// 定时器清理
const useInterval = (callback, delay) => {
const savedCallback = useRef();

useEffect(() => {
savedCallback.current = callback;
}, [callback]);

useEffect(() => {
if (delay !== null) {
const id = setInterval(() => savedCallback.current(), delay);
return () => clearInterval(id);
}
}, [delay]);
};

🎯 实战优化案例

电商产品列表优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
import React, { useState, useMemo, useCallback, memo } from 'react';
import { FixedSizeList as List } from 'react-window';

// 优化后的产品卡片组件
const ProductCard = memo(({ product, onAddToCart, onToggleFavorite }) => {
const [imageLoaded, setImageLoaded] = useState(false);

const handleAddToCart = useCallback(() => {
onAddToCart(product.id);
}, [product.id, onAddToCart]);

const handleToggleFavorite = useCallback(() => {
onToggleFavorite(product.id);
}, [product.id, onToggleFavorite]);

return (
<div className="product-card">
<div className="product-image-container">
{!imageLoaded && <div className="image-skeleton" />}
<img
src={product.image}
alt={product.name}
onLoad={() => setImageLoaded(true)}
style={{ display: imageLoaded ? 'block' : 'none' }}
loading="lazy"
/>
</div>

<div className="product-info">
<h3 className="product-name">{product.name}</h3>
<p className="product-price">¥{product.price}</p>
<div className="product-rating">
{'★'.repeat(Math.floor(product.rating))}
<span>({product.reviewCount})</span>
</div>
</div>

<div className="product-actions">
<button onClick={handleAddToCart} className="add-to-cart-btn">
加入购物车
</button>
<button
onClick={handleToggleFavorite}
className={`favorite-btn ${product.isFavorite ? 'active' : ''}`}
>
♥
</button>
</div>
</div>
);
}, (prevProps, nextProps) => {
// 自定义比较函数,只有关键属性变化时才重新渲染
return (
prevProps.product.id === nextProps.product.id &&
prevProps.product.price === nextProps.product.price &&
prevProps.product.isFavorite === nextProps.product.isFavorite
);
});

// 优化后的产品列表组件
const OptimizedProductList = ({ products, filters, onAddToCart, onToggleFavorite }) => {
// 使用 useMemo 缓存过滤和排序结果
const filteredAndSortedProducts = useMemo(() => {
let result = products;

// 应用过滤器
if (filters.category) {
result = result.filter(p => p.category === filters.category);
}

if (filters.priceRange) {
result = result.filter(p =>
p.price >= filters.priceRange[0] && p.price <= filters.priceRange[1]
);
}

if (filters.rating) {
result = result.filter(p => p.rating >= filters.rating);
}

// 应用排序
if (filters.sortBy) {
result = [...result].sort((a, b) => {
switch (filters.sortBy) {
case 'price-asc':
return a.price - b.price;
case 'price-desc':
return b.price - a.price;
case 'rating':
return b.rating - a.rating;
case 'name':
return a.name.localeCompare(b.name);
default:
return 0;
}
});
}

return result;
}, [products, filters]);

// 使用 useCallback 缓存事件处理函数
const handleAddToCart = useCallback((productId) => {
onAddToCart(productId);
}, [onAddToCart]);

const handleToggleFavorite = useCallback((productId) => {
onToggleFavorite(productId);
}, [onToggleFavorite]);

// 虚拟化列表渲染函数
const Row = ({ index, style }) => {
const product = filteredAndSortedProducts[index];

return (
<div style={style}>
<ProductCard
product={product}
onAddToCart={handleAddToCart}
onToggleFavorite={handleToggleFavorite}
/>
</div>
);
};

return (
<div className="product-list-container">
<div className="product-list-header">
<span>共 {filteredAndSortedProducts.length} 个产品</span>
</div>

<List
height={600}
itemCount={filteredAndSortedProducts.length}
itemSize={300}
width="100%"
>
{Row}
</List>
</div>
);
};

export default OptimizedProductList;

📈 性能优化检查清单

开发阶段

  • 使用 React.memo 包装纯组件
  • 合理使用 useMemo 和 useCallback
  • 避免在渲染函数中创建内联对象和函数
  • 使用稳定的 key 属性
  • 实现代码分割和懒加载
  • 优化状态结构,避免不必要的重新渲染

构建阶段

  • 配置代码分割策略
  • 启用 Tree Shaking
  • 压缩和混淆代码
  • 优化图片和静态资源
  • 配置 CDN 加速

运行时阶段

  • 实现虚拟化长列表
  • 使用图片懒加载
  • 实现预加载关键资源
  • 监控性能指标
  • 防止内存泄漏

结语

React性能优化是一个持续的过程,需要在开发的各个阶段都保持关注。通过合理的组件设计、状态管理、代码分割和资源优化,我们可以构建出高性能的React应用。

记住,过早优化是万恶之源,但忽视性能同样危险。在开发过程中,要平衡开发效率和应用性能,在关键路径上进行针对性优化。

最重要的是,要建立性能监控体系,通过数据驱动优化决策,让用户享受到流畅的应用体验。


你在React性能优化方面有什么经验和技巧?欢迎在评论区分享你的见解!