You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
6.3 KiB
6.3 KiB
性能优化
文档版本:v1.1
最后更新:2025-01-18
目录
1. 代码分割
1.1 路由级代码分割
// 路由级代码分割
const EditorPage = lazy(() => import('@/pages/EditorPage'));
// 组件级代码分割
const HeavyComponent = lazy(() => import('@components/features/HeavyComponent'));
// 使用时包裹 Suspense
<Suspense fallback={<LoadingSpinner />}>
<HeavyComponent />
</Suspense>
1.2 动态导入
// 按需加载大型库
const handleExport = async () => {
const { exportToVideo } = await import("@/utils/videoExport");
await exportToVideo(data);
};
2. 虚拟列表
使用 @tanstack/react-virtual 处理长列表:
import { useVirtualizer } from '@tanstack/react-virtual';
function VirtualList({ items }: { items: Item[] }) {
const parentRef = useRef<HTMLDivElement>(null);
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 80, // 预估每项高度
overscan: 5, // 预渲染数量
});
return (
<div ref={parentRef} className="h-full overflow-auto">
<div
style={{
height: `${virtualizer.getTotalSize()}px`,
width: '100%',
position: 'relative',
}}
>
{virtualizer.getVirtualItems().map((virtualItem) => (
<div
key={virtualItem.key}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: `${virtualItem.size}px`,
transform: `translateY(${virtualItem.start}px)`,
}}
>
<ItemComponent item={items[virtualItem.index]} />
</div>
))}
</div>
</div>
);
}
3. 组件优化
3.1 使用 memo
// 使用 memo 避免不必要渲染
const StoryboardItem = memo(function StoryboardItem({
storyboard,
isSelected,
onSelect
}: StoryboardItemProps) {
return (
<div
className={cn(
'p-3 rounded-md cursor-pointer',
isSelected && 'bg-primary/10 border-l-2 border-primary'
)}
onClick={() => onSelect(storyboard.id)}
>
{/* ... */}
</div>
);
});
3.2 使用 useMemo
// 使用 useMemo 缓存计算结果
const sortedStoryboards = useMemo(
() => storyboards.sort((a, b) => a.order - b.order),
[storyboards],
);
3.3 使用 useCallback
// 使用 useCallback 缓存回调
const handleSelect = useCallback(
(id: string) => {
selectStoryboard(id);
},
[selectStoryboard],
);
4. 图片优化
4.1 懒加载图片
// 图片懒加载组件
function LazyImage({ src, alt, className }: LazyImageProps) {
const [loaded, setLoaded] = useState(false);
const imgRef = useRef<HTMLImageElement>(null);
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting && imgRef.current) {
imgRef.current.src = src;
observer.disconnect();
}
},
{ rootMargin: '100px' }
);
if (imgRef.current) {
observer.observe(imgRef.current);
}
return () => observer.disconnect();
}, [src]);
return (
<div className={cn('relative', className)}>
{!loaded && <div className="absolute inset-0 bg-muted animate-pulse" />}
<img
ref={imgRef}
alt={alt}
className={cn(
'w-full h-full object-cover transition-opacity',
loaded ? 'opacity-100' : 'opacity-0'
)}
onLoad={() => setLoaded(true)}
/>
</div>
);
}
4.2 响应式图片
<picture>
<source
srcSet={`${image}-small.webp`}
media="(max-width: 640px)"
type="image/webp"
/>
<source
srcSet={`${image}-medium.webp`}
media="(max-width: 1024px)"
type="image/webp"
/>
<img src={`${image}-large.webp`} alt={alt} />
</picture>
5. Debounce 和 Throttle
5.1 useDebounce Hook
// src/hooks/useDebounce.ts
import { useState, useEffect } from "react";
export function useDebounce<T>(value: T, delay: number): T {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => clearTimeout(handler);
}, [value, delay]);
return debouncedValue;
}
5.2 使用示例
function SearchInput() {
const [search, setSearch] = useState('');
const debouncedSearch = useDebounce(search, 300);
useEffect(() => {
if (debouncedSearch) {
// 执行搜索
performSearch(debouncedSearch);
}
}, [debouncedSearch]);
return <input value={search} onChange={(e) => setSearch(e.target.value)} />;
}
5.3 useThrottle Hook
// src/hooks/useThrottle.ts
import { useRef, useCallback } from "react";
export function useThrottle<T extends (...args: any[]) => any>(
callback: T,
delay: number,
): T {
const lastRun = useRef(Date.now());
return useCallback(
(...args: Parameters<T>) => {
const now = Date.now();
if (now - lastRun.current >= delay) {
callback(...args);
lastRun.current = now;
}
},
[callback, delay],
) as T;
}
6. 性能监控
6.1 React DevTools Profiler
import { Profiler } from "react";
function onRenderCallback(
id: string,
phase: "mount" | "update",
actualDuration: number,
) {
console.log(`${id} (${phase}) took ${actualDuration}ms`);
}
<Profiler id="StoryboardList" onRender={onRenderCallback}>
<StoryboardList />
</Profiler>;
6.2 Web Vitals
// src/lib/vitals.ts
import { onCLS, onFID, onFCP, onLCP, onTTFB } from "web-vitals";
export function reportWebVitals() {
onCLS(console.log);
onFID(console.log);
onFCP(console.log);
onLCP(console.log);
onTTFB(console.log);
}
相关文档
最后更新:2025-01-18 | 版本:v1.1