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
ProjectResourcePanel 性能优化
日期: 2026-01-28
类型: 性能优化
影响范围: 前端 - 资源管理面板
概述
基于 React Best Practices 对 ProjectResourcePanel 组件进行全面性能优化,包括 Bundle Size 优化、图片加载优化、Tab 切换优化和 Re-render 优化。
优化内容
1. Bundle Size 优化
问题:使用 barrel imports 导致不必要的代码打包
解决方案:
- 拆分
@/components/ui/dropdown-menu为直接导入 - 使用
React.lazy延迟加载 Dialog 相关组件 - 仅在需要时加载删除确认弹窗
代码变更:
// Before
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
// After
import { DropdownMenu } from '@/components/ui/dropdown-menu';
import { DropdownMenuContent } from '@/components/ui/dropdown-menu';
import { DropdownMenuItem } from '@/components/ui/dropdown-menu';
import { DropdownMenuTrigger } from '@/components/ui/dropdown-menu';
// Dialog 延迟加载
const Dialog = lazy(() => import('@/components/ui/dialog').then(m => ({ default: m.Dialog })));
收益:
- 减少初始 bundle size
- 提升首屏加载速度
- 删除弹窗按需加载
2. 图片加载优化(已修正)
问题:
- 图片加载无过渡效果,用户体验差
- 每次切换 tab,图片都重新显示 loading,即使已加载过
解决方案:
- 为
ResourceGridItem添加imageLoading状态 - 检查图片是否已在浏览器缓存中
- 已缓存的图片直接显示,不显示 loading skeleton
- 使用
loading="lazy"和decoding="async"优化加载 - 添加淡入动画(
transition-opacity duration-300)
代码变更:
const [imageLoading, setImageLoading] = useState(true);
// 检查图片是否已在浏览器缓存中
const imageUrl = resource.thumbnailUrl || resource.fileUrl;
const isImageCached = useMemo(() => {
if (!imageUrl) return false;
const img = new Image();
img.src = imageUrl;
return img.complete && img.naturalHeight !== 0;
}, [imageUrl]);
// 如果图片已缓存,直接设置为已加载
useEffect(() => {
if (isImageCached) {
setImageLoading(false);
}
}, [isImageCached]);
{/* Loading Skeleton - 仅在首次加载时显示 */}
{imageLoading && !isImageCached && (
<div className="absolute inset-0 flex h-full w-full items-center justify-center bg-fill-darker animate-pulse">
<Icon className="h-1/3 w-1/3 text-text-secondary/30" />
</div>
)}
<img
loading="lazy"
decoding="async"
className={cn(
'h-full w-full transition-opacity duration-300',
imageLoading && !isImageCached ? 'opacity-0' : 'opacity-100'
)}
onLoad={() => setImageLoading(false)}
/>
收益:
- 提升用户体验
- 避免图片突然出现
- 清晰的加载状态反馈
- 已缓存图片立即显示,无 loading 闪烁
- 利用浏览器原生懒加载优化性能
3. Tab 切换优化(已修正)
问题:
- Tab 切换时频繁显示 loading,影响用户体验
- Loading 和空状态同时闪烁
解决方案:
- 添加
loadedTabsSet 记录已加载过的 tab - 仅首次加载时显示 loading
- Loading 遮罩仅覆盖列表区域,不遮挡 tab 选项
- 优化渲染逻辑:loading → 内容(列表或空状态),避免同时显示
代码变更:
const [loadedTabs, setLoadedTabs] = useState<Set<string>>(new Set([RESOURCE_TYPES.CHARACTER]));
// 标记当前 tab 为已加载
useEffect(() => {
if (!isLoading && resources && resources.length >= 0 && !loadedTabs.has(filterType)) {
const timer = setTimeout(() => {
setLoadedTabs((prev) => new Set(prev).add(filterType));
}, 0);
return () => clearTimeout(timer);
}
}, [isLoading, resources, filterType, loadedTabs]);
// 判断是否需要显示 loading(首次加载且未加载过该 tab)
const shouldShowLoading = isLoading && !loadedTabs.has(filterType);
// 优化渲染逻辑:loading → 内容,避免闪烁
{shouldShowLoading ? (
<LoadingSpinner />
) : error ? (
<EmptyState error />
) : (
<VirtualList ... />
)}
收益:
- 避免频繁的 loading 闪烁
- Tab 选项始终可见和可交互
- 更流畅的切换体验
- 仅首次加载显示 loading
- 避免 loading 和空状态同时出现
4. Re-render 优化
问题:不必要的 useMemo 和多个 useEffect 导致额外渲染
解决方案:
- 简化不必要的
useMemo(常量数据、简单逻辑) - 合并相关的
useEffect - 移除空状态组件的
useMemo(JSX 本身已足够轻量)
代码变更:
// Before
const tabs: TabItem[] = useMemo(
() => RESOURCE_CATEGORIES.map(...),
[selectedProjectId]
);
// After - RESOURCE_CATEGORIES 是常量,无需 memo
const tabs: TabItem[] = RESOURCE_CATEGORIES.map(...);
// Before - 两个独立的 useEffect
useEffect(() => { /* projectId 变化 */ }, [selectedProjectId, ...]);
useEffect(() => { /* 清除选中状态 */ }, [selectedResourceId, ...]);
// After - 合并相关逻辑
useEffect(() => {
// projectId 变化逻辑
// 清除选中状态逻辑
}, [selectedProjectId, filterType, searchQuery, selectedResourceId, selectedResources]);
收益:
- 减少不必要的重渲染
- 简化代码逻辑
- 提升运行时性能
5. 样式优化
问题:使用了非标准的 Tailwind 类名
解决方案:
- 将
bg-gradient-to-t修正为bg-linear-to-t
性能指标
预期提升:
- Bundle Size: 减少 ~5-10KB(Dialog 延迟加载)
- 首屏加载: 提升 ~100-200ms
- Tab 切换: 更流畅的 UI 响应
- 图片加载: 更好的用户体验
测试建议
-
Bundle Size 测试:
npm run build # 对比优化前后的 bundle size -
功能测试:
- 切换不同 Tab,验证 loading 状态
- 上传图片,验证 loading skeleton
- 删除资源,验证 Dialog 延迟加载
- 多选资源,验证选中状态
-
性能测试:
- 使用 React DevTools Profiler 对比优化前后
- 验证 re-render 次数减少
- 验证 Tab 切换响应时间
相关文件
client/src/components/features/project/ProjectResourcePanel.tsx
参考
- React Best Practices Skill
- Vercel Engineering - React Performance Guidelines