# 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 相关组件
- 仅在需要时加载删除确认弹窗
**代码变更**:
```typescript
// 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. 图片加载优化(已修正)
**问题**:
1. 图片加载无过渡效果,用户体验差
2. 每次切换 tab,图片都重新显示 loading,即使已加载过
**解决方案**:
- 为 `ResourceGridItem` 添加 `imageLoading` 状态
- 检查图片是否已在浏览器缓存中
- 已缓存的图片直接显示,不显示 loading skeleton
- 使用 `loading="lazy"` 和 `decoding="async"` 优化加载
- 添加淡入动画(`transition-opacity duration-300`)
**代码变更**:
```typescript
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 && (
)}
setImageLoading(false)}
/>
```
**收益**:
- 提升用户体验
- 避免图片突然出现
- 清晰的加载状态反馈
- 已缓存图片立即显示,无 loading 闪烁
- 利用浏览器原生懒加载优化性能
### 3. Tab 切换优化(已修正)
**问题**:
1. Tab 切换时频繁显示 loading,影响用户体验
2. Loading 和空状态同时闪烁
**解决方案**:
- 添加 `loadedTabs` Set 记录已加载过的 tab
- 仅首次加载时显示 loading
- Loading 遮罩仅覆盖列表区域,不遮挡 tab 选项
- 优化渲染逻辑:loading → 内容(列表或空状态),避免同时显示
**代码变更**:
```typescript
const [loadedTabs, setLoadedTabs] = useState>(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 ? (
) : error ? (
) : (
)}
```
**收益**:
- 避免频繁的 loading 闪烁
- Tab 选项始终可见和可交互
- 更流畅的切换体验
- 仅首次加载显示 loading
- 避免 loading 和空状态同时出现
### 4. Re-render 优化
**问题**:不必要的 `useMemo` 和多个 `useEffect` 导致额外渲染
**解决方案**:
- 简化不必要的 `useMemo`(常量数据、简单逻辑)
- 合并相关的 `useEffect`
- 移除空状态组件的 `useMemo`(JSX 本身已足够轻量)
**代码变更**:
```typescript
// 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 响应
- 图片加载: 更好的用户体验
## 测试建议
1. **Bundle Size 测试**:
```bash
npm run build
# 对比优化前后的 bundle size
```
2. **功能测试**:
- 切换不同 Tab,验证 loading 状态
- 上传图片,验证 loading skeleton
- 删除资源,验证 Dialog 延迟加载
- 多选资源,验证选中状态
3. **性能测试**:
- 使用 React DevTools Profiler 对比优化前后
- 验证 re-render 次数减少
- 验证 Tab 切换响应时间
## 相关文件
- `client/src/components/features/project/ProjectResourcePanel.tsx`
## 参考
- React Best Practices Skill
- [Vercel Engineering - React Performance Guidelines](https://github.com/vercel-labs/agent-skills/tree/react-best-practices/skills/react-best-practices)