名称:react-performance 用户可调用:false 描述:用于React性能优化,包括记忆化、懒加载和虚拟化。用于优化React应用程序。 允许工具:
- Bash
- 读取
React 性能优化
掌握React性能优化,用于构建高性能、可扩展的React应用程序,采用行业最佳实践。
React.memo 和组件记忆化
React.memo通过记忆化组件输出来防止不必要的重新渲染:
import { memo } from 'react';
interface Props {
name: string;
onClick: () => void;
}
// 基本记忆化
const ExpensiveComponent = memo(
function ExpensiveComponent({ name, onClick }: Props) {
console.log('渲染 ExpensiveComponent');
return <button onClick={onClick}>{name}</button>;
});
// 自定义比较函数
const CustomMemo = memo(
function Component({ user }: { user: User }) {
return <div>{user.name}</div>;
},
(prevProps, nextProps) => {
// 如果传递nextProps会返回与prevProps相同的结果,则返回true
return prevProps.user.id === nextProps.user.id;
}
);
// 何时使用自定义比较
const ProductCard = memo(
function ProductCard({ product }: { product: Product }) {
return (
<div>
<h3>{product.name}</h3>
<p>${product.price}</p>
</div>
);
},
(prev, next) => {
// 仅当这些特定字段更改时重新渲染
return (
prev.product.id === next.product.id &&
prev.product.name === next.product.name &&
prev.product.price === next.product.price
);
}
);
useMemo 用于昂贵计算
import { useMemo, useState } from 'react';
function DataTable({ items }: { items: Item[] }) {
const [filter, setFilter] = useState('');
const [sortBy, setSortBy] = useState<'name' | 'price'>('name');
// 昂贵的过滤和排序
const processedItems = useMemo(() => {
console.log('计算过滤和排序的项目');
return items
.filter(item => item.name.toLowerCase().includes(filter.toLowerCase()))
.sort((a, b) => {
if (sortBy === 'name') {
return a.name.localeCompare(b.name);
}
return a.price - b.price;
});
}, [items, filter, sortBy]);
// 昂贵的聚合计算
const statistics = useMemo(() => {
console.log('计算统计');
return {
total: processedItems.reduce((sum, item) => sum + item.price, 0),
average: processedItems.length
? processedItems.reduce((sum, item) => sum + item.price, 0) / processedItems.length
: 0,
count: processedItems.length
};
}, [processedItems]);
return (
<>
<input value={filter} onChange={(e) => setFilter(e.target.value)} />
<select value={sortBy} onChange={(e) => setSortBy(e.target.value as any)}>
<option value="name">名称</option>
<option value="price">价格</option>
</select>
<div>总计: ${statistics.total}</div>
<div>平均: ${statistics.average.toFixed(2)}</div>
<div>数量: {statistics.count}</div>
{processedItems.map(item => (
<div key={item.id}>{item.name} - ${item.price}</div>
))}
</>
);
}
useCallback 用于稳定函数引用
import { useCallback, useState, memo } from 'react';
// 子组件,仅在必要时重新渲染
const ListItem = memo(function ListItem({
item,
onDelete
}: {
item: Item;
onDelete: (id: string) => void;
}) {
console.log('渲染 ListItem', item.id);
return (
<div>
{item.name}
<button onClick={() => onDelete(item.id)}>删除</button>
</div>
);
});
function OptimizedList({ items }: { items: Item[] }) {
const [deletedIds, setDeletedIds] = useState<Set<string>>(new Set());
// 不使用useCallback,每次渲染都创建新函数,导致即使使用memo,ListItem也会重新渲染
const handleDelete = useCallback((id: string) => {
setDeletedIds(prev => new Set([...prev, id]));
// API调用删除
api.deleteItem(id);
}, []); // 空依赖意味着函数从不更改
const handleDeleteWithDeps = useCallback((id: string) => {
console.log('已删除:', deletedIds.size);
setDeletedIds(prev => new Set([...prev, id]));
}, [deletedIds]); // 当deletedIds更改时重新创建
const visibleItems = items.filter(item => !deletedIds.has(item.id));
return (
<>
{visibleItems.map(item => (
<ListItem key={item.id} item={item} onDelete={handleDelete} />
))}
</>
);
}
使用React.lazy和Suspense进行代码分割
import { lazy, Suspense } from 'react';
import { Routes, Route } from 'react-router-dom';
// 懒加载路由组件
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Profile = lazy(() => import('./pages/Profile'));
const Settings = lazy(() => import('./pages/Settings'));
const Analytics = lazy(() => import('./pages/Analytics'));
// 回退组件
function LoadingSpinner() {
return <div className="spinner">加载中...</div>;
}
function App() {
return (
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/profile" element={<Profile />} />
<Route path="/settings" element={<Settings />} />
<Route path="/analytics" element={<Analytics />} />
</Routes>
</Suspense>
);
}
// 悬停预加载以改善用户体验
function Navigation() {
const preloadDashboard = () => import('./pages/Dashboard');
const preloadProfile = () => import('./pages/Profile');
return (
<nav>
<a href="/dashboard" onMouseEnter={preloadDashboard}>仪表板</a>
<a href="/profile" onMouseEnter={preloadProfile}>个人资料</a>
</nav>
);
}
// 嵌套Suspense边界
function DashboardLayout() {
const Header = lazy(() => import('./components/Header'));
const Sidebar = lazy(() => import('./components/Sidebar'));
const Content = lazy(() => import('./components/Content'));
return (
<div className="dashboard">
<Suspense fallback={<div>加载头部...</div>}>
<Header />
</Suspense>
<Suspense fallback={<div>加载侧边栏...</div>}>
<Sidebar />
</Suspense>
<Suspense fallback={<div>加载内容...</div>}>
<Content />
</Suspense>
</div>
);
}
大型列表的虚拟滚动
import { FixedSizeList, VariableSizeList } from 'react-window';
import AutoSizer from 'react-virtualized-auto-sizer';
// 固定大小项目
function VirtualList({ items }: { items: string[] }) {
const Row = ({ index, style }: {
index: number;
style: React.CSSProperties
}) => (
<div style={style} className="list-item">
{items[index]}
</div>
);
return (
<FixedSizeList
height={600}
itemCount={items.length}
itemSize={35}
width="100%"
>
{Row}
</FixedSizeList>
);
}
// 可变大小项目
function VariableList({ items }: { items: Post[] }) {
const getItemSize = (index: number) => {
// 基于内容计算高度
return items[index].content.length > 100 ? 120 : 80;
};
const Row = ({ index, style }: any) => (
<div style={style} className="post">
<h3>{items[index].title}</h3>
<p>{items[index].content}</p>
</div>
);
return (
<VariableSizeList
height={600}
itemCount={items.length}
itemSize={getItemSize}
width="100%"
>
{Row}
</VariableSizeList>
);
}
// 使用AutoSizer响应式布局
function ResponsiveList({ items }: { items: Item[] }) {
return (
<div style={{ height: '100vh', width: '100%' }}>
<AutoSizer>
{({ height, width }) => (
<FixedSizeList
height={height}
itemCount={items.length}
itemSize={50}
width={width}
>
{({ index, style }) => (
<div style={style}>{items[index].name}</div>
)}
</FixedSizeList>
)}
</AutoSizer>
</div>
);
}
使用React Profiler API进行性能监控
import { Profiler, ProfilerOnRenderCallback } from 'react';
const onRenderCallback: ProfilerOnRenderCallback = (
id, // 刚刚提交的Profiler树的“id”属性
phase, // “mount”(首次渲染)或“update”(重新渲染)
actualDuration, // 渲染提交更新所花费的时间
baseDuration, // 估计无记忆化渲染整个子树的时间
startTime, // React开始渲染此更新的时间
commitTime, // React提交此更新的时间
interactions // 属于此更新的交互集合
) => {
console.log(`${id} (${phase}) 花费 ${actualDuration}ms`);
// 发送到分析
if (actualDuration > 100) {
analytics.track('slow-render', {
component: id,
duration: actualDuration,
phase
});
}
};
function App() {
return (
<Profiler id="App" onRender={onRenderCallback}>
<Dashboard />
</Profiler>
);
}
// 嵌套分析器用于细粒度监控
function Dashboard() {
return (
<div>
<Profiler id="Sidebar" onRender={onRenderCallback}>
<Sidebar />
</Profiler>
<Profiler id="Content" onRender={onRenderCallback}>
<Content />
</Profiler>
</div>
);
}
包大小优化
// 使用动态导入处理大型库
function ChartComponent() {
const [Chart, setChart] = useState<any>(null);
useEffect(() => {
// 仅当需要时加载图表库
import('chart.js').then(module => {
setChart(() => module.Chart);
});
}, []);
if (!Chart) return <div>加载图表...</div>;
return <Chart data={data} />;
}
// 树可摇动导入
// 良好:仅导入所需内容
import { format } from 'date-fns';
// 不良:导入整个库
import moment from 'moment';
// 使用webpack魔术注释指定块名称
const AdminPanel = lazy(() =>
import(/* webpackChunkName: "admin" */ './AdminPanel')
);
const UserDashboard = lazy(() =>
import(/* webpackChunkName: "dashboard" */ './UserDashboard')
);
图像优化技术
import { useState, useEffect } from 'react';
// 懒加载图像
function LazyImage({ src, alt, placeholder }: {
src: string;
alt: string;
placeholder?: string;
}) {
const [imageSrc, setImageSrc] = useState(placeholder || '');
const [imageRef, setImageRef] = useState<HTMLImageElement | null>(null);
useEffect(() => {
if (!imageRef) return;
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
setImageSrc(src);
observer.unobserve(imageRef);
}
});
});
observer.observe(imageRef);
return () => {
if (imageRef) observer.unobserve(imageRef);
};
}, [imageRef, src]);
return (
<img
ref={setImageRef}
src={imageSrc}
alt={alt}
loading="lazy"
/>
);
}
// 渐进式图像加载
function ProgressiveImage({ src, placeholder }: {
src: string;
placeholder: string;
}) {
const [currentSrc, setCurrentSrc] = useState(placeholder);
const [loading, setLoading] = useState(true);
useEffect(() => {
const img = new Image();
img.src = src;
img.onload = () => {
setCurrentSrc(src);
setLoading(false);
};
}, [src]);
return (
<img
src={currentSrc}
style={{
filter: loading ? 'blur(10px)' : 'none',
transition: 'filter 0.3s'
}}
/>
);
}
并发特性:useTransition 和 useDeferredValue
import { useState, useTransition, useDeferredValue } from 'react';
// useTransition用于非紧急更新
function SearchResults() {
const [query, setQuery] = useState('');
const [results, setResults] = useState<Result[]>([]);
const [isPending, startTransition] = useTransition();
const handleSearch = (value: string) => {
setQuery(value); // 紧急:立即更新输入
// 非紧急:延迟昂贵搜索
startTransition(() => {
const searchResults = performExpensiveSearch(value);
setResults(searchResults);
});
};
return (
<>
<input
value={query}
onChange={e => handleSearch(e.target.value)}
placeholder="搜索..."
/>
{isPending && <div>搜索中...</div>}
<ResultsList results={results} />
</>
);
}
// useDeferredValue用于延迟昂贵渲染
function ProductList({ products }: { products: Product[] }) {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
// 使用延迟值过滤,保持UI响应
const filteredProducts = useMemo(() => {
return products.filter(p =>
p.name.toLowerCase().includes(deferredQuery.toLowerCase())
);
}, [products, deferredQuery]);
return (
<>
<input
value={query}
onChange={e => setQuery(e.target.value)}
placeholder="过滤产品..."
/>
{query !== deferredQuery && <div>更新中...</div>}
<div>
{filteredProducts.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
</>
);
}
防抖和节流
import { useState, useEffect, useCallback, useRef } from 'react';
// 自定义防抖钩子
function useDebounce<T>(value: T, delay: number): T {
const [debouncedValue, setDebouncedValue] = useState<T>(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => clearTimeout(handler);
}, [value, delay]);
return debouncedValue;
}
// 搜索中使用
function SearchWithDebounce() {
const [query, setQuery] = useState('');
const debouncedQuery = useDebounce(query, 500);
useEffect(() => {
if (debouncedQuery) {
// 仅在用户停止输入500毫秒后搜索
performSearch(debouncedQuery);
}
}, [debouncedQuery]);
return (
<input
value={query}
onChange={e => setQuery(e.target.value)}
placeholder="搜索..."
/>
);
}
// 自定义节流钩子
function useThrottle<T>(value: T, limit: number): T {
const [throttledValue, setThrottledValue] = useState<T>(value);
const lastRan = useRef(Date.now());
useEffect(() => {
const handler = setTimeout(() => {
if (Date.now() - lastRan.current >= limit) {
setThrottledValue(value);
lastRan.current = Date.now();
}
}, limit - (Date.now() - lastRan.current));
return () => clearTimeout(handler);
}, [value, limit]);
return throttledValue;
}
// 节流滚动事件
function InfiniteScroll() {
const [scrollPosition, setScrollPosition] = useState(0);
const throttledScroll = useThrottle(scrollPosition, 200);
useEffect(() => {
const handleScroll = () => setScrollPosition(window.scrollY);
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
useEffect(() => {
// 每200毫秒处理一次滚动
console.log('节流滚动位置:', throttledScroll);
}, [throttledScroll]);
return <div>滚动位置: {throttledScroll}</div>;
}
优化上下文性能
import { createContext, useContext, useState, useMemo, ReactNode } from 'react';
// 拆分上下文以防止不必要的重新渲染
const StateContext = createContext<State | null>(null);
const DispatchContext = createContext<Dispatch | null>(null);
function Provider({ children }: { children: ReactNode }) {
const [state, setState] = useState<State>(initialState);
// 记忆化dispatch以保持稳定
const dispatch = useMemo(
() => ({
updateUser: (user: User) => setState(s => ({ ...s, user })),
updateSettings: (settings: Settings) => setState(s => ({ ...s, settings }))
}),
[]
);
return (
<StateContext.Provider value={state}>
<DispatchContext.Provider value={dispatch}>
{children}
</DispatchContext.Provider>
</StateContext.Provider>
);
}
// 组件仅在使用实际更改的状态时重新渲染
function UserProfile() {
const state = useContext(StateContext); // 任何状态更改时重新渲染
return <div>{state?.user.name}</div>;
}
function SettingsButton() {
const dispatch = useContext(DispatchContext); // 从不重新渲染
return <button onClick={() => dispatch?.updateSettings({})}>设置</button>;
}
使用Web Workers进行繁重计算
import { useEffect, useState } from 'react';
// worker.ts
// self.addEventListener('message', (e) => {
// const result = performHeavyCalculation(e.data);
// self.postMessage(result);
// });
function useWebWorker<T, R>(workerFn: (data: T) => R) {
const [result, setResult] = useState<R | null>(null);
const [error, setError] = useState<Error | null>(null);
const [loading, setLoading] = useState(false);
const execute = (data: T) => {
setLoading(true);
setError(null);
const worker = new Worker(
URL.createObjectURL(
new Blob([`(${workerFn.toString()})()`], { type: 'application/javascript' })
)
);
worker.postMessage(data);
worker.onmessage = (e) => {
setResult(e.data);
setLoading(false);
worker.terminate();
};
worker.onerror = (e) => {
setError(new Error(e.message));
setLoading(false);
worker.terminate();
};
};
return { result, error, loading, execute };
}
// 使用
function DataProcessor() {
const { result, loading, execute } = useWebWorker(
(data: number[]) => {
// 繁重计算在Worker中运行
return data.reduce((sum, n) => sum + n * n, 0);
}
);
const handleProcess = () => {
execute(Array.from({ length: 1000000 }, (_, i) => i));
};
return (
<div>
<button onClick={handleProcess} disabled={loading}>
处理数据
</button>
{loading && <div>处理中...</div>}
{result && <div>结果: {result}</div>}
</div>
);
}
何时使用此技能
使用react-performance时,当您需要:
- 优化慢渲染组件
- 通过代码分割减少包大小
- 使用虚拟化处理大型列表
- 防止不必要的重新渲染
- 改善应用程序加载时间
- 优化昂贵计算
- 构建高性能React应用程序
- 调试性能问题
- 实施懒加载策略
- 提高核心Web指标分数
- 优化移动设备
- 高效处理实时数据
最佳实践
-
优化前进行性能分析 - 使用React DevTools Profiler识别实际瓶颈,然后再应用优化。
-
明智使用React.memo - 仅记忆化频繁渲染且道具相同的组件,或具有昂贵渲染逻辑的组件。
-
记忆化回调和值 - 使用useCallback处理传递给记忆化子组件的函数,useMemo处理昂贵计算。
-
按路由进行代码分割 - 懒加载路由组件以减少初始包大小并改善加载时间。
-
虚拟化长列表 - 对于超过100个项目的列表,使用react-window或react-virtualized。
-
优化图像 - 懒加载图像,使用适当格式(如WebP),实施渐进式加载。
-
防抖昂贵操作 - 防抖搜索输入、API调用和其他昂贵操作。
-
策略性拆分上下文 - 分离读取和写入上下文以防止不必要的消费者重新渲染。
-
监控包大小 - 使用webpack-bundle-analyzer识别和移除大型依赖。
-
使用并发特性 - 利用useTransition和useDeferredValue改善感知性能。
常见陷阱
-
过早优化 - 不要未经测量就优化。先分析,再优化瓶颈。
-
过度使用memo - 记忆化所有内容会增加开销。仅当有可测量益处时记忆化。
-
错误的依赖项 - useMemo/useCallback中缺失依赖项会导致闭包陈旧和错误。
-
未测量影响 - 始终使用React Profiler或浏览器工具测量性能改进。
-
忽略包大小 - 为小功能导入大型库会显著影响加载时间。
-
记忆化原始值 - 对于原始值或简单计算,useMemo是不必要的。
-
不使用key属性 - 列表中缺少或不正确的key会导致不必要的重新渲染和错误。
-
内联函数定义 - 在JSX中内联创建函数会阻止React.memo有效工作。
-
未进行代码分割 - 一次性加载整个应用会显著增加初始加载时间。
-
忘记网络优化 - 优化数据获取,使用分页,实施适当的缓存策略。