Appearance
渲染优化案例分析 - 实战性能优化示例
1. 案例1: 电商商品列表
1.1 初始问题代码
typescript
// 问题版本: 性能差
function ProductList({ products, onAddToCart }) {
const [filter, setFilter] = useState('');
const [sort, setSort] = useState('name');
return (
<div>
<div>
<input
placeholder="搜索商品..."
value={filter}
onChange={(e) => setFilter(e.target.value)}
/>
<select value={sort} onChange={(e) => setSort(e.target.value)}>
<option value="name">按名称</option>
<option value="price">按价格</option>
</select>
</div>
<div className="products">
{products
.filter(p => p.name.toLowerCase().includes(filter.toLowerCase()))
.sort((a, b) => {
if (sort === 'name') return a.name.localeCompare(b.name);
return a.price - b.price;
})
.map(product => (
<div key={product.id} className="product-card">
<img src={product.image} alt={product.name} />
<h3>{product.name}</h3>
<p>${product.price}</p>
<button onClick={() => onAddToCart(product)}>
加入购物车
</button>
</div>
))}
</div>
</div>
);
}
// 性能问题:
// 1. 每次渲染都filter+sort (即使数据未变)
// 2. 每次渲染都创建新的onClick函数
// 3. products变化时重渲染所有商品卡片
// 4. 大量商品时滚动卡顿1.2 优化步骤
typescript
// 步骤1: useMemo优化计算
function ProductListV2({ products, onAddToCart }) {
const [filter, setFilter] = useState('');
const [sort, setSort] = useState('name');
// ✓ 缓存过滤和排序结果
const displayProducts = useMemo(() => {
return products
.filter(p => p.name.toLowerCase().includes(filter.toLowerCase()))
.sort((a, b) => {
if (sort === 'name') return a.name.localeCompare(b.name);
return a.price - b.price;
});
}, [products, filter, sort]);
return (
<div>
<FilterControls
filter={filter}
onFilterChange={setFilter}
sort={sort}
onSortChange={setSort}
/>
<div className="products">
{displayProducts.map(product => (
<ProductCard
key={product.id}
product={product}
onAddToCart={onAddToCart}
/>
))}
</div>
</div>
);
}
// 步骤2: 拆分组件
const FilterControls = React.memo(({ filter, onFilterChange, sort, onSortChange }) => {
return (
<div>
<input
placeholder="搜索商品..."
value={filter}
onChange={(e) => onFilterChange(e.target.value)}
/>
<select value={sort} onChange={(e) => onSortChange(e.target.value)}>
<option value="name">按名称</option>
<option value="price">按价格</option>
</select>
</div>
);
});
// 步骤3: memo商品卡片
const ProductCard = React.memo(({ product, onAddToCart }) => {
const handleClick = useCallback(() => {
onAddToCart(product);
}, [product, onAddToCart]);
return (
<div className="product-card">
<img src={product.image} alt={product.name} loading="lazy" />
<h3>{product.name}</h3>
<p>${product.price}</p>
<button onClick={handleClick}>加入购物车</button>
</div>
);
});
// 步骤4: 虚拟滚动(商品很多时)
import { FixedSizeGrid } from 'react-window';
function VirtualProductList({ products, onAddToCart }) {
const [filter, setFilter] = useState('');
const [sort, setSort] = useState('name');
const displayProducts = useMemo(() => {
return products
.filter(p => p.name.toLowerCase().includes(filter.toLowerCase()))
.sort((a, b) => {
if (sort === 'name') return a.name.localeCompare(b.name);
return a.price - b.price;
});
}, [products, filter, sort]);
const Cell = ({ columnIndex, rowIndex, style }) => {
const index = rowIndex * 3 + columnIndex;
if (index >= displayProducts.length) return null;
const product = displayProducts[index];
return (
<div style={style}>
<ProductCard product={product} onAddToCart={onAddToCart} />
</div>
);
};
return (
<div>
<FilterControls {...controlProps} />
<FixedSizeGrid
columnCount={3}
columnWidth={200}
height={600}
rowCount={Math.ceil(displayProducts.length / 3)}
rowHeight={300}
width={620}
>
{Cell}
</FixedSizeGrid>
</div>
);
}
// 性能提升:
// 初始渲染: 从2000ms -> 200ms (10倍)
// 过滤/排序: 从500ms -> 50ms (10倍)
// 滚动: 60fps流畅
// 内存: 减少90%2. 案例2: 实时聊天应用
2.2 问题分析
typescript
// 问题版本
function ChatApp() {
const [messages, setMessages] = useState([]);
const [input, setInput] = useState('');
const [onlineUsers, setOnlineUsers] = useState([]);
// 问题1: 新消息导致整个列表重渲染
// 问题2: input变化导致消息列表重渲染
// 问题3: onlineUsers变化导致消息列表重渲染
return (
<div className="chat-app">
<div className="sidebar">
<h3>在线用户 ({onlineUsers.length})</h3>
<ul>
{onlineUsers.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
<div className="main">
<div className="messages">
{messages.map(msg => (
<div key={msg.id} className="message">
<span>{msg.user}: </span>
<span>{msg.text}</span>
<span>{new Date(msg.timestamp).toLocaleTimeString()}</span>
</div>
))}
</div>
<div className="input-area">
<input
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyPress={(e) => {
if (e.key === 'Enter') {
sendMessage(input);
setInput('');
}
}}
/>
<button onClick={() => {
sendMessage(input);
setInput('');
}}>
发送
</button>
</div>
</div>
</div>
);
}2.3 优化方案
typescript
// 优化版本
function ChatApp() {
return (
<div className="chat-app">
<OnlineUsers />
<ChatMain />
</div>
);
}
// 组件1: 在线用户列表
const OnlineUsers = React.memo(function OnlineUsers() {
const [users, setUsers] = useState([]);
useEffect(() => {
const unsubscribe = subscribeToOnlineUsers(setUsers);
return unsubscribe;
}, []);
return (
<div className="sidebar">
<h3>在线用户 ({users.length})</h3>
<ul>
{users.map(user => (
<UserItem key={user.id} user={user} />
))}
</ul>
</div>
);
});
const UserItem = React.memo(({ user }) => {
return <li>{user.name}</li>;
});
// 组件2: 聊天主区域
function ChatMain() {
return (
<div className="main">
<MessageList />
<MessageInput />
</div>
);
}
// 组件3: 消息列表
const MessageList = React.memo(function MessageList() {
const [messages, setMessages] = useState([]);
const bottomRef = useRef(null);
useEffect(() => {
const unsubscribe = subscribeToMessages((newMsg) => {
setMessages(prev => [...prev, newMsg]);
});
return unsubscribe;
}, []);
useEffect(() => {
bottomRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages.length]);
return (
<div className="messages">
{messages.map(msg => (
<Message key={msg.id} message={msg} />
))}
<div ref={bottomRef} />
</div>
);
});
const Message = React.memo(({ message }) => {
const time = useMemo(
() => new Date(message.timestamp).toLocaleTimeString(),
[message.timestamp]
);
return (
<div className="message">
<span>{message.user}: </span>
<span>{message.text}</span>
<span>{time}</span>
</div>
);
});
// 组件4: 输入框
const MessageInput = React.memo(function MessageInput() {
const [input, setInput] = useState('');
const handleSend = useCallback(() => {
if (input.trim()) {
sendMessage(input);
setInput('');
}
}, [input]);
const handleKeyPress = useCallback((e) => {
if (e.key === 'Enter') {
handleSend();
}
}, [handleSend]);
return (
<div className="input-area">
<input
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyPress={handleKeyPress}
/>
<button onClick={handleSend}>发送</button>
</div>
);
});
// 性能提升:
// - input变化不影响消息列表
// - 新消息只渲染新增项
// - onlineUsers变化不影响聊天区域
// - 每个部分独立优化3. 案例3: 数据仪表板
3.1 问题代码
typescript
function Dashboard() {
const [data, setData] = useState(null);
const [dateRange, setDateRange] = useState('week');
const [loading, setLoading] = useState(true);
useEffect(() => {
setLoading(true);
fetchDashboardData(dateRange).then(result => {
setData(result);
setLoading(false);
});
}, [dateRange]);
if (loading) return <Loading />;
// 问题: 所有图表在一个组件,任何更新都重渲染全部
return (
<div className="dashboard">
<div className="controls">
<button onClick={() => setDateRange('day')}>日</button>
<button onClick={() => setDateRange('week')}>周</button>
<button onClick={() => setDateRange('month')}>月</button>
</div>
<div className="charts">
{/* 昂贵的图表组件 */}
<LineChart data={data.sales} />
<BarChart data={data.revenue} />
<PieChart data={data.categories} />
<AreaChart data={data.traffic} />
</div>
<div className="tables">
<DataTable data={data.orders} columns={orderColumns} />
<DataTable data={data.products} columns={productColumns} />
</div>
</div>
);
}3.2 优化方案
typescript
// 优化版本
function Dashboard() {
const [dateRange, setDateRange] = useState('week');
return (
<div className="dashboard">
<DateRangeControls value={dateRange} onChange={setDateRange} />
<Suspense fallback={<ChartsSkeleton />}>
<DashboardCharts dateRange={dateRange} />
</Suspense>
<Suspense fallback={<TablesSkeleton />}>
<DashboardTables dateRange={dateRange} />
</Suspense>
</div>
);
}
// 日期范围控制
const DateRangeControls = React.memo(({ value, onChange }) => {
return (
<div className="controls">
<button onClick={() => onChange('day')}>日</button>
<button onClick={() => onChange('week')}>周</button>
<button onClick={() => onChange('month')}>月</button>
</div>
);
});
// 图表区域
function DashboardCharts({ dateRange }) {
const data = use(fetchChartsData(dateRange));
return (
<div className="charts">
<LazyLineChart data={data.sales} />
<LazyBarChart data={data.revenue} />
<LazyPieChart data={data.categories} />
<LazyAreaChart data={data.traffic} />
</div>
);
}
// 懒加载图表
const LazyLineChart = lazy(() => import('./charts/LineChart'));
const LazyBarChart = lazy(() => import('./charts/BarChart'));
const LazyPieChart = lazy(() => import('./charts/PieChart'));
const LazyAreaChart = lazy(() => import('./charts/AreaChart'));
// 表格区域
function DashboardTables({ dateRange }) {
const data = use(fetchTablesData(dateRange));
return (
<div className="tables">
<VirtualDataTable
data={data.orders}
columns={orderColumns}
/>
<VirtualDataTable
data={data.products}
columns={productColumns}
/>
</div>
);
}
// 虚拟滚动表格
const VirtualDataTable = React.memo(({ data, columns }) => {
return (
<FixedSizeList
height={400}
itemCount={data.length}
itemSize={50}
>
{({ index, style }) => (
<TableRow
key={data[index].id}
style={style}
data={data[index]}
columns={columns}
/>
)}
</FixedSizeList>
);
});
// 性能提升:
// - 按需加载图表库
// - Suspense并行加载数据
// - 虚拟滚动处理大数据表格
// - 组件独立更新4. 总结
渲染优化案例分析的核心要点:
- 组件拆分: 隔离更新范围
- useMemo: 缓存昂贵计算
- React.memo: 避免不必要渲染
- 虚拟滚动: 处理大列表
- 懒加载: 按需加载组件
- Suspense: 并行数据获取
通过实际案例掌握性能优化技巧。
5. 高级渲染优化案例
5.1 社交媒体Feed优化
jsx
// 场景:类似Twitter的无限滚动Feed
// 挑战:数以千计的帖子,包含图片、视频、互动按钮
// 优化方案
import { useInfiniteQuery } from '@tanstack/react-query';
import { useVirtual } from 'react-virtual';
function SocialFeed() {
const parentRef = useRef();
// 无限查询
const {
data,
fetchNextPage,
hasNextPage,
isFetchingNextPage
} = useInfiniteQuery({
queryKey: ['feed'],
queryFn: ({ pageParam = 0 }) => fetchFeed(pageParam),
getNextPageParam: (lastPage) => lastPage.nextCursor
});
// 展平所有页面的帖子
const allPosts = data?.pages.flatMap(page => page.posts) ?? [];
// 虚拟滚动
const rowVirtualizer = useVirtual({
size: allPosts.length,
parentRef,
estimateSize: useCallback(() => 200, []),
overscan: 5
});
// 监听滚动到底部
useEffect(() => {
const [lastItem] = [...rowVirtualizer.virtualItems].reverse();
if (!lastItem) return;
if (
lastItem.index >= allPosts.length - 1 &&
hasNextPage &&
!isFetchingNextPage
) {
fetchNextPage();
}
}, [
hasNextPage,
fetchNextPage,
allPosts.length,
isFetchingNextPage,
rowVirtualizer.virtualItems
]);
return (
<div ref={parentRef} style={{ height: '100vh', overflow: 'auto' }}>
<div
style={{
height: `${rowVirtualizer.totalSize}px`,
position: 'relative'
}}
>
{rowVirtualizer.virtualItems.map(virtualRow => {
const post = allPosts[virtualRow.index];
return (
<div
key={virtualRow.index}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
transform: `translateY(${virtualRow.start}px)`
}}
>
<PostCard post={post} />
</div>
);
})}
</div>
{isFetchingNextPage && <LoadingSpinner />}
</div>
);
}
const PostCard = memo(({ post }) => {
const [isLiked, setIsLiked] = useState(post.liked);
const [likeCount, setLikeCount] = useState(post.likes);
// 乐观更新
const handleLike = useCallback(async () => {
setIsLiked(prev => !prev);
setLikeCount(prev => prev + (isLiked ? -1 : 1));
try {
await likePost(post.id);
} catch (error) {
// 回滚
setIsLiked(prev => !prev);
setLikeCount(prev => prev + (isLiked ? 1 : -1));
}
}, [post.id, isLiked]);
return (
<div className="post-card">
<PostHeader author={post.author} />
<PostContent content={post.content} />
{post.media && <LazyMedia media={post.media} />}
<PostActions
isLiked={isLiked}
likeCount={likeCount}
onLike={handleLike}
/>
</div>
);
}, (prev, next) => {
// 自定义比较:只在帖子ID变化时重渲染
return prev.post.id === next.post.id;
});
// 性能提升:
// - 初始加载:从2s降到300ms
// - 滚动FPS:稳定60fps
// - 内存占用:控制在200MB以内
// - 支持10000+帖子流畅滚动5.2 拖拽排序列表优化
jsx
// 场景:Trello风格的看板,支持拖拽
// 挑战:拖拽时的实时更新、大量卡片
import { DndContext, closestCenter } from '@dnd-kit/core';
import {
arrayMove,
SortableContext,
useSortable,
verticalListSortingStrategy
} from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
function KanbanBoard() {
const [columns, setColumns] = useState({
todo: [],
inProgress: [],
done: []
});
const handleDragEnd = useCallback((event) => {
const { active, over } = event;
if (!over || active.id === over.id) return;
// 批量状态更新
setColumns(prev => {
// 找到源列和目标列
const sourceColumn = findColumn(prev, active.id);
const targetColumn = findColumn(prev, over.id);
if (sourceColumn === targetColumn) {
// 同列排序
const oldIndex = prev[sourceColumn].findIndex(item => item.id === active.id);
const newIndex = prev[sourceColumn].findIndex(item => item.id === over.id);
return {
...prev,
[sourceColumn]: arrayMove(prev[sourceColumn], oldIndex, newIndex)
};
} else {
// 跨列移动
const item = prev[sourceColumn].find(item => item.id === active.id);
return {
...prev,
[sourceColumn]: prev[sourceColumn].filter(item => item.id !== active.id),
[targetColumn]: [...prev[targetColumn], item]
};
}
});
}, []);
return (
<DndContext collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
<div className="kanban-board">
{Object.entries(columns).map(([columnId, items]) => (
<Column key={columnId} id={columnId} items={items} />
))}
</div>
</DndContext>
);
}
const Column = memo(({ id, items }) => {
return (
<div className="column">
<h2>{id}</h2>
<SortableContext
items={items.map(item => item.id)}
strategy={verticalListSortingStrategy}
>
{items.map(item => (
<SortableCard key={item.id} item={item} />
))}
</SortableContext>
</div>
);
});
const SortableCard = memo(({ item }) => {
const {
attributes,
listeners,
setNodeRef,
transform,
transition,
isDragging
} = useSortable({ id: item.id });
const style = {
transform: CSS.Transform.toString(transform),
transition,
opacity: isDragging ? 0.5 : 1
};
return (
<div ref={setNodeRef} style={style} {...attributes} {...listeners}>
<CardContent item={item} />
</div>
);
});
// 性能提升:
// - 拖拽响应:<16ms
// - 大量卡片(1000+):流畅
// - 内存:稳定5.3 富文本编辑器优化
jsx
// 场景:类似Notion的富文本编辑器
// 挑战:实时协作、大文档、复杂格式
import { useMemo } from 'react';
import { createEditor, Transforms } from 'slate';
import { Slate, Editable, withReact } from 'slate-react';
import { withHistory } from 'slate-history';
function RichTextEditor({ initialValue, onChange }) {
// 编辑器实例只创建一次
const editor = useMemo(
() => withHistory(withReact(createEditor())),
[]
);
// 渲染元素的缓存
const renderElement = useCallback((props) => {
switch (props.element.type) {
case 'heading':
return <Heading {...props} />;
case 'list':
return <List {...props} />;
case 'code':
return <CodeBlock {...props} />;
default:
return <Paragraph {...props} />;
}
}, []);
// 渲染叶子节点的缓存
const renderLeaf = useCallback((props) => {
return <Leaf {...props} />;
}, []);
// 防抖保存
const debouncedOnChange = useMemo(
() => debounce((value) => onChange(value), 500),
[onChange]
);
return (
<Slate
editor={editor}
value={initialValue}
onChange={debouncedOnChange}
>
<Toolbar editor={editor} />
<Editable
renderElement={renderElement}
renderLeaf={renderLeaf}
placeholder="开始输入..."
spellCheck
autoFocus
/>
</Slate>
);
}
// 优化的元素组件
const Heading = memo(({ attributes, children, element }) => {
const level = element.level || 1;
const Tag = `h${level}`;
return <Tag {...attributes}>{children}</Tag>;
}, (prev, next) => {
return (
prev.element.level === next.element.level &&
prev.children === next.children
);
});
const CodeBlock = memo(({ attributes, children }) => {
return (
<pre {...attributes}>
<code>{children}</code>
</pre>
);
});
const Leaf = memo(({ attributes, children, leaf }) => {
let content = children;
if (leaf.bold) {
content = <strong>{content}</strong>;
}
if (leaf.italic) {
content = <em>{content}</em>;
}
if (leaf.code) {
content = <code>{content}</code>;
}
return <span {...attributes}>{content}</span>;
});
// 性能提升:
// - 输入延迟:<10ms
// - 大文档(10000+行):流畅编辑
// - 内存:稳定增长6. React 19特性优化案例
6.1 使用Compiler自动优化
jsx
// 优化前:手动添加优化
function ProductFilters({ products, onFilterChange }) {
const [category, setCategory] = useState('all');
const [priceRange, setPriceRange] = useState([0, 1000]);
const [inStock, setInStock] = useState(false);
const filteredProducts = useMemo(() => {
return products.filter(product => {
if (category !== 'all' && product.category !== category) {
return false;
}
if (product.price < priceRange[0] || product.price > priceRange[1]) {
return false;
}
if (inStock && !product.inStock) {
return false;
}
return true;
});
}, [products, category, priceRange, inStock]);
const handleCategoryChange = useCallback((e) => {
setCategory(e.target.value);
}, []);
const handlePriceChange = useCallback((range) => {
setPriceRange(range);
}, []);
useEffect(() => {
onFilterChange(filteredProducts);
}, [filteredProducts, onFilterChange]);
return (
<div>
{/* 过滤器UI */}
</div>
);
}
// 优化后:React Compiler自动优化
function ProductFilters({ products, onFilterChange }) {
const [category, setCategory] = useState('all');
const [priceRange, setPriceRange] = useState([0, 1000]);
const [inStock, setInStock] = useState(false);
// Compiler自动memoize
const filteredProducts = products.filter(product => {
if (category !== 'all' && product.category !== category) {
return false;
}
if (product.price < priceRange[0] || product.price > priceRange[1]) {
return false;
}
if (inStock && !product.inStock) {
return false;
}
return true;
});
// Compiler自动优化handlers
const handleCategoryChange = (e) => {
setCategory(e.target.value);
};
const handlePriceChange = (range) => {
setPriceRange(range);
};
useEffect(() => {
onFilterChange(filteredProducts);
}, [filteredProducts, onFilterChange]);
return (
<div>
{/* 过滤器UI */}
</div>
);
}
// 代码简化:减少30%样板代码
// 性能:与手动优化相同6.2 使用use()Hook优化数据加载
jsx
// 优化前:传统方式
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function loadData() {
setLoading(true);
try {
const userData = await fetchUser(userId);
setUser(userData);
const postsData = await fetchUserPosts(userId);
setPosts(postsData);
} finally {
setLoading(false);
}
}
loadData();
}, [userId]);
if (loading) return <Loading />;
return (
<div>
<UserInfo user={user} />
<PostList posts={posts} />
</div>
);
}
// 优化后:use() + Suspense
function UserProfile({ userId }) {
// 并行加载
const user = use(fetchUser(userId));
const posts = use(fetchUserPosts(userId));
return (
<div>
<UserInfo user={user} />
<PostList posts={posts} />
</div>
);
}
// 父组件使用Suspense
function ProfilePage({ userId }) {
return (
<Suspense fallback={<ProfileSkeleton />}>
<UserProfile userId={userId} />
</Suspense>
);
}
// 性能提升:
// - 代码简化:减少50%状态管理代码
// - 并行加载:自动并发
// - 错误处理:ErrorBoundary统一处理6.3 Server Components优化
jsx
// app/dashboard/page.tsx (Server Component)
async function DashboardPage() {
// 在服务器直接获取数据
const stats = await getStats();
const recentOrders = await getRecentOrders();
const topProducts = await getTopProducts();
return (
<div className="dashboard">
<h1>仪表盘</h1>
<div className="stats-grid">
{stats.map(stat => (
<StatCard key={stat.id} stat={stat} />
))}
</div>
<div className="dashboard-content">
{/* Server Component */}
<RecentOrders orders={recentOrders} />
{/* Client Component用于交互 */}
<TopProductsChart data={topProducts} />
</div>
</div>
);
}
// Server Component:无需客户端JS
function RecentOrders({ orders }) {
return (
<div className="orders">
<h2>最近订单</h2>
<table>
{orders.map(order => (
<tr key={order.id}>
<td>{order.id}</td>
<td>{order.customer}</td>
<td>{order.total}</td>
</tr>
))}
</table>
</div>
);
}
// Client Component:需要交互
'use client';
function TopProductsChart({ data }) {
return (
<div className="chart">
<h2>热销产品</h2>
<Chart data={data} />
</div>
);
}
// 性能提升:
// - Bundle减少:Server Component不打包到客户端
// - SEO友好:服务器渲染,完整HTML
// - 加载速度:减少70% JavaScript
// - 数据获取:服务器直连数据库,更快7. 性能监控与持续优化
7.1 自动化性能测试
javascript
// performance.test.js
import { render, screen } from '@testing-library/react';
import { measureRenderTime } from './test-utils';
describe('ProductList Performance', () => {
it('应该在100ms内渲染1000个产品', async () => {
const products = generateProducts(1000);
const renderTime = await measureRenderTime(() => {
render(<ProductList products={products} />);
});
expect(renderTime).toBeLessThan(100);
});
it('滚动应该维持60fps', async () => {
const products = generateProducts(10000);
render(<ProductList products={products} />);
const fps = await measureScrollFPS();
expect(fps).toBeGreaterThan(55); // 允许小波动
});
});
// 性能测试工具
function measureRenderTime(renderFn) {
return new Promise((resolve) => {
const start = performance.now();
renderFn();
requestIdleCallback(() => {
const end = performance.now();
resolve(end - start);
});
});
}7.2 生产环境监控
jsx
// 性能监控组件
function PerformanceMonitor({ children }) {
useEffect(() => {
// 监控长任务
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.duration > 50) {
// 上报长任务
reportMetric({
type: 'long-task',
duration: entry.duration,
startTime: entry.startTime
});
}
}
});
observer.observe({ entryTypes: ['longtask'] });
return () => observer.disconnect();
}, []);
useEffect(() => {
// 监控内存
const checkMemory = () => {
if (performance.memory) {
const usage = performance.memory.usedJSHeapSize;
const limit = performance.memory.jsHeapSizeLimit;
const percentage = (usage / limit) * 100;
if (percentage > 90) {
reportMetric({
type: 'memory-warning',
usage,
percentage
});
}
}
};
const intervalId = setInterval(checkMemory, 30000);
return () => clearInterval(intervalId);
}, []);
return children;
}7.3 性能预算
javascript
// performance-budget.config.js
module.exports = {
budgets: [
{
name: 'JavaScript',
limit: '300kb',
threshold: 0.9
},
{
name: 'CSS',
limit: '50kb',
threshold: 0.9
},
{
name: 'Images',
limit: '500kb',
threshold: 0.9
},
{
name: 'FCP',
limit: '1.5s',
threshold: 0.9
},
{
name: 'LCP',
limit: '2.5s',
threshold: 0.9
}
]
};
// CI/CD中的性能检查
const checkPerformanceBudget = async () => {
const report = await runLighthouse();
const budgets = require('./performance-budget.config');
let passed = true;
for (const budget of budgets.budgets) {
const actual = report.metrics[budget.name];
const limit = parseSize(budget.limit);
if (actual > limit) {
console.error(`❌ ${budget.name}: ${actual} exceeds limit ${budget.limit}`);
passed = false;
} else {
console.log(`✅ ${budget.name}: ${actual} within limit ${budget.limit}`);
}
}
if (!passed) {
process.exit(1);
}
};8. 面试重点
8.1 渲染优化面试题
typescript
const interviewQuestions = [
{
q: '如何优化一个包含10000条数据的列表?',
a: `
1. 虚拟滚动:只渲染可见项(react-window)
2. 分页:每页显示50-100条
3. 无限滚动:逐步加载
4. React.memo:避免列表项重渲染
5. key优化:使用稳定的唯一ID
`
},
{
q: '拖拽功能如何优化性能?',
a: `
1. 使用transform代替top/left
2. 降低更新频率(throttle)
3. 使用will-change提示浏览器
4. 减少拖拽时的DOM操作
5. 使用专业库(dnd-kit、react-dnd)
`
},
{
q: 'React 19的Compiler如何提升性能?',
a: `
1. 自动分析依赖
2. 自动插入memoization
3. 减少手动优化代码
4. 编译时优化,无运行时开销
5. 性能接近手动优化
`
},
{
q: 'Server Components的性能优势?',
a: `
1. 减少客户端Bundle体积
2. 服务器直连数据库,更快
3. SEO友好,完整HTML
4. 自动代码分割
5. 流式渲染,渐进式加载
`
},
{
q: '如何监控React应用的性能?',
a: `
1. React DevTools Profiler
2. Chrome Performance面板
3. Web Vitals(LCP, FID, CLS)
4. PerformanceObserver API
5. 第三方监控(Sentry, LogRocket)
`
}
];8.2 优化检查清单
typescript
const renderOptimizationChecklist = {
'组件层面': [
'使用React.memo包裹纯展示组件',
'合理拆分组件,控制更新范围',
'避免在render中创建新对象/数组',
'使用key优化列表渲染',
'考虑使用React Compiler'
],
'Hook层面': [
'使用useMemo缓存昂贵计算',
'使用useCallback稳定函数引用',
'避免useEffect中的不必要依赖',
'考虑useTransition延迟更新'
],
'大列表': [
'虚拟滚动(react-window)',
'分页或无限滚动',
'图片懒加载',
'骨架屏提升体验'
],
'数据加载': [
'使用Suspense并行加载',
'考虑Server Components',
'实现数据预取',
'合理的错误边界'
],
'监控': [
'设置性能预算',
'监控Core Web Vitals',
'使用Profiler API',
'CI/CD性能检查'
]
};