用户看到内容的时间
主要内容加载完成时间
页面交互响应时间
视觉稳定性指标
将代码分割成更小的包,按需加载
// 所有组件一次性导入
import Home from './pages/Home';
import About from './pages/About';
import Contact from './pages/Contact';
import Dashboard from './pages/Dashboard';
import Admin from './pages/Admin';
// 使用React.lazy进行懒加载
import React, { lazy, Suspense } from 'react';
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Contact = lazy(() => import('./pages/Contact'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Admin = lazy(() => import('./pages/Admin'));
// 路由配置中使用Suspense
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Suspense>
React.lazy()进行组件懒加载缓存计算结果,避免不必要的重新计算和渲染
渲染次数: 0
渲染次数: 0
import React, { memo } from 'react';
// 普通函数组件 - 每次父组件渲染都会重新渲染
const RegularComponent = ({ data }) => {
console.log('RegularComponent 渲染了');
return <div>{data}</div>;
};
// 使用React.memo包装 - 只有props变化时才会重新渲染
const MemoizedComponent = memo(({ data }) => {
console.log('MemoizedComponent 渲染了');
return <div>{data}</div>;
});
// 自定义比较函数
const CustomMemoComponent = memo(
({ data }) => <div>{data}</div>,
(prevProps, nextProps) => {
// 只有当data长度变化时才重新渲染
return prevProps.data.length === nextProps.data.length;
}
);
import React, { useMemo } from 'react';
function ExpensiveComponent({ items, filter }) {
// 使用useMemo缓存昂贵的计算结果
const filteredItems = useMemo(() => {
console.log('重新计算过滤项...');
return items.filter(item =>
item.name.toLowerCase().includes(filter.toLowerCase())
);
}, [items, filter]); // 依赖项变化时才重新计算
// 另一个例子:计算派生数据
const stats = useMemo(() => {
const total = items.length;
const completed = items.filter(i => i.completed).length;
const percentage = total > 0 ? (completed / total) * 100 : 0;
return { total, completed, percentage };
}, [items]);
return (
<div>
<p>过滤后项数: {filteredItems.length}</p>
<p>完成率: {stats.percentage.toFixed(1)}%</p>
</div>
);
}
import React, { useState, useCallback } from 'react';
function ParentComponent() {
const [count, setCount] = useState(0);
const [items, setItems] = useState([]);
// ❌ 错误:每次渲染都创建新函数
// const handleAdd = () => {
// setItems([...items, `Item ${items.length}`]);
// };
// ✅ 正确:使用useCallback缓存函数
const handleAdd = useCallback(() => {
setItems(prev => [...prev, `Item ${prev.length}`]);
}, []); // 空依赖数组,函数不会重新创建
// 当依赖项变化时重新创建
const handleIncrement = useCallback(() => {
setCount(count + 1);
}, [count]); // count变化时重新创建函数
return (
<div>
<ChildComponent onAdd={handleAdd} />
<button onClick={handleIncrement}>增加计数</button>
<p>计数: {count}</p>
</div>
);
}
const ChildComponent = memo(({ onAdd }) => {
console.log('ChildComponent 渲染了');
return <button onClick={onAdd}>添加项</button>;
});
只渲染可见区域的列表项,提升滚动性能
import React, { useMemo } from 'react';
import { FixedSizeList } from 'react-window';
// 普通长列表 - 性能差
function BadList({ items }) {
return (
<div style={{ height: '400px', overflowY: 'auto' }}>
{items.map((item, index) => (
<div key={index} style={{
padding: '20px',
borderBottom: '1px solid #ccc'
}}>
{item}
</div>
))}
</div>
);
}
// 使用react-window虚拟化列表
function GoodList({ items }) {
const Row = ({ index, style }) => (
<div style={style}>
项 #{index}: {items[index]}
</div>
);
return (
<FixedSizeList
height={400}
width={300}
itemCount={items.length}
itemSize={50} // 每项高度
>
{Row}
</FixedSizeList>
);
}
// 自定义虚拟列表实现
function VirtualList({ items, itemHeight, visibleCount }) {
const [scrollTop, setScrollTop] = useState(0);
const containerHeight = itemHeight * visibleCount;
const startIndex = Math.floor(scrollTop / itemHeight);
const endIndex = Math.min(
startIndex + visibleCount + 1,
items.length
);
const visibleItems = useMemo(() => {
return items.slice(startIndex, endIndex);
}, [items, startIndex, endIndex]);
return (
<div
style={{ height: containerHeight, overflowY: 'auto' }}
onScroll={e => setScrollTop(e.target.scrollTop)}
>
<div style={{
height: items.length * itemHeight,
position: 'relative'
}}>
{visibleItems.map((item, index) => (
<div
key={startIndex + index}
style={{
position: 'absolute',
top: (startIndex + index) * itemHeight,
width: '100%',
height: itemHeight
}}
>
{item}
</div>
))}
</div>
</div>
);
}
识别和避免不必要的组件重新渲染
// ❌ 错误:每次渲染都创建新对象
function BadComponent({ items }) {
return (
<ChildComponent
style={{ color: 'red', fontSize: '16px' }} // 新对象
config={{ showHeader: true, autoSave: false }} // 新对象
onClick={() => console.log('clicked')} // 新函数
/>
);
}
// ✅ 正确:使用useMemo和useCallback
function GoodComponent({ items }) {
const style = useMemo(() => ({
color: 'red', fontSize: '16px'
}), []);
const config = useMemo(() => ({
showHeader: true,
autoSave: false
}), []);
const handleClick = useCallback(() => {
console.log('clicked');
}, []);
return (
<ChildComponent
style={style}
config={config}
onClick={handleClick}
/>
);
}
// ✅ 或者将静态值提取到组件外部
const STATIC_STYLE = { color: 'red', fontSize: '16px' };
const STATIC_CONFIG = { showHeader: true, autoSave: false };
function BetterComponent({ items }) {
const handleClick = useCallback(() => {
console.log('clicked');
}, []);
return (
<ChildComponent
style={STATIC_STYLE}
config={STATIC_CONFIG}
onClick={handleClick}
/>
);
}
优化图片、字体和其他静态资源加载
// 1. 使用适当的图片格式
// WebP (现代浏览器) > JPEG (照片) > PNG (透明) > SVG (矢量)
// 2. 使用懒加载
<img
src="thumbnail.jpg"
data-src="large.jpg"
loading="lazy"
alt="描述"
className="lazy-image"
/>
// 3. 使用srcset和sizes
<img
src="image-800w.jpg"
srcSet="image-400w.jpg 400w,
image-800w.jpg 800w,
image-1200w.jpg 1200w"
sizes="(max-width: 600px) 400px,
(max-width: 1200px) 800px,
1200px"
alt="响应式图片"
/>
// 4. 使用图片CDN
const imageUrl = `https://cdn.example.com/${width}x${height}/${quality}/image.jpg`;
// 5. 使用React-lazyload或类似库
import LazyLoad from 'react-lazyload';
<LazyLoad height={200} offset={100}>
<img src="large-image.jpg" alt="懒加载图片" />
</LazyLoad>
// 使用font-display: swap避免FOIT
@font-face {
font-family: 'CustomFont';
src: url('font.woff2') format('woff2');
font-display: swap; /* 显示回退字体直到自定义字体加载完成 */
}
// 预加载关键字体
<link
rel="preload"
href="/fonts/custom.woff2"
as="font"
type="font/woff2"
crossorigin
/>
// 使用fontfaceobserver控制字体加载
import FontFaceObserver from 'fontfaceobserver';
const font = new FontFaceObserver('CustomFont');
font.load().then(() => {
document.documentElement.classList.add('fonts-loaded');
});
优化Webpack配置和构建输出
// webpack.config.js
module.exports = {
// ... 其他配置
optimization: {
// 1. 代码分割
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
commons: {
name: 'commons',
minChunks: 2,
chunks: 'initial',
minSize: 0,
},
},
},
// 2. 最小化
minimize: true,
minimizer: [
new TerserPlugin({
parallel: true,
terserOptions: {
compress: {
drop_console: process.env.NODE_ENV === 'production',
},
},
}),
],
// 3. 运行时chunk
runtimeChunk: 'single',
},
// 4. 生产模式优化
mode: 'production',
devtool: 'source-map',
// 5. 缓存
cache: {
type: 'filesystem',
},
// 6. 排除大型库
externals: {
lodash: '_',
jquery: 'jQuery',
},
};
// 使用@swc/core替换babel
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: 'swc-loader',
options: {
jsc: {
parser: {
syntax: 'ecmascript',
jsx: true,
},
transform: {
react: {
runtime: 'automatic',
},
},
},
},
},
}
// 使用esbuild-loader
{
test: /\.(js|jsx)$/,
loader: 'esbuild-loader',
options: {
loader: 'jsx',
target: 'es2015',
},
}
使用工具监控和分析应用性能
// 1. 安装React Developer Tools浏览器扩展
// 2. 使用Profiler API进行编程式分析
import { Profiler } from 'react';
function App() {
const handleRender = (
id, // 发生提交的 Profiler 树的 "id"
phase, // "mount" 或 "update"
actualDuration, // 本次更新 committed 花费的渲染时间
baseDuration, // 估计不使用 memoization 的情况下渲染整棵子树需要的时间
startTime, // 本次更新中 React 开始渲染的时间
commitTime, // 本次更新中 React committed 的时间
interactions // 属于本次更新的 interactions 的集合
) => {
console.log({
id,
phase,
actualDuration,
baseDuration,
startTime,
commitTime,
});
};
return (
<Profiler id="App" onRender={handleRender}>
<YourApp />
</Profiler>
);
}
import { getCLS, getFID, getLCP } from 'web-vitals';
// 发送数据到分析服务
function sendToAnalytics(metric) {
const body = JSON.stringify(metric);
navigator.sendBeacon('/analytics', body);
// 或者使用Google Analytics
gtag('event', metric.name, {
value: metric.value,
event_label: metric.id,
non_interaction: true,
});
}
// 监控所有核心指标
getCLS(sendToAnalytics);
getFID(sendToAnalytics);
getLCP(sendToAnalytics);
// React专用性能钩子
import { useWebVitals } from 'react-web-vitals';
function App() {
useWebVitals((metric) => {
console.log(metric);
});
return <YourApp />;
}
// package.json中添加性能预算
{
"name": "my-app",
"version": "1.0.0",
"scripts": {
"build": "webpack --profile --json > stats.json",
"analyze": "webpack-bundle-analyzer stats.json"
},
"performance": {
"budgets": [
{
"type": "bundle",
"name": "main",
"maximumSize": "200 KB"
},
{
"type": "initial",
"maximumSize": "400 KB"
},
{
"type": "asset",
"name": "*.jpg",
"maximumSize": "100 KB"
}
]
}
}
// 使用Lighthouse CI自动化测试
module.exports = {
ci: {
collect: {
numberOfRuns: 3,
url: ['http://localhost:3000'],
},
assert: {
assertions: {
'categories:performance': ['error', { minScore: 0.9 }],
'categories:accessibility': ['error', { minScore: 0.9 }],
'categories:best-practices': ['error', { minScore: 0.9 }],
'categories:seo': ['error', { minScore: 0.9 }],
},
},
},
};
| 工具名称 | 用途 | 类型 |
|---|---|---|
| React Developer Tools | 组件树查看、性能分析、状态调试 | 浏览器扩展 |
| Webpack Bundle Analyzer | 分析打包文件大小和依赖关系 | 构建工具 |
| Lighthouse | 全面性能、PWA、可访问性测试 | 测试工具 |
| react-window | 虚拟化长列表和网格 | React组件库 |
| why-did-you-render | 检测不必要的重新渲染 | 调试工具 |
| @welldone-software/why-did-you-render | 更现代的重新渲染检测 | 调试工具 |
| web-vitals | 测量核心Web性能指标 | 测量库 |
| Lighthouse CI | 自动化性能测试 | CI工具 |