# app/page.tsx 优化实现指南 ## 📋 实现步骤 ### 第一阶段:快速修复(预期时间:1-2 小时) #### 步骤 1: 修复 useMemo 依赖项 **文件**: `app/page.tsx` **修改位置 1**: 第 ~280-290 行 - recentMembers ```typescript // 修改前 const recentMembers = useMemo(() => { return Object.values(treeData.members) .sort((a, b) => parseInt(b.id) - parseInt(a.id)) .slice(0, 5) }, [treeData]) // 修改后 const recentMembers = useMemo(() => { return Object.values(treeData.members) .sort((a, b) => parseInt(b.id) - parseInt(a.id)) .slice(0, 5) }, [treeData.members]) ``` **修改位置 2**: 第 ~300-330 行 - allPhotos ```typescript // 修改前 const allPhotos = useMemo(() => { // ... 代码 }, [treeData, isOwner]) // 修改后 const allPhotos = useMemo(() => { // ... 代码 }, [treeData.members, isOwner]) ``` **修改位置 3**: 第 ~400-500 行 - monthlyAnniversaries ```typescript // 修改前 const monthlyAnniversaries = useMemo(() => { // ... 代码 }, [treeData]) // 修改后 const monthlyAnniversaries = useMemo(() => { // ... 代码 }, [treeData.members]) ``` **修改位置 4**: 第 ~550-570 行 - locationGroups ```typescript // 修改前 const locationGroups = useMemo(() => { // ... 代码 }, [treeData]) // 修改后 const locationGroups = useMemo(() => { // ... 代码 }, [treeData.members]) ``` **修改位置 5**: 第 ~200-250 行 - stats ```typescript // 修改前 const stats = useMemo(() => { // ... 代码 }, [treeData]) // 修改后 const stats = useMemo(() => { // ... 代码 }, [treeData.members]) ``` --- #### 步骤 2: 添加照片分组 useMemo **文件**: `app/page.tsx` **位置**: 在 `allPhotos` useMemo 之后添加 ```typescript // 添加新的 useMemo const groupedPhotosByMonth = useMemo(() => { const sortedPhotos = [...allPhotos].sort((a, b) => new Date(b.uploadedAt).getTime() - new Date(a.uploadedAt).getTime() ) const groupedByMonth: Record = {} sortedPhotos.forEach(photo => { const monthKey = format(new Date(photo.uploadedAt), 'yyyy年MM月') if (!groupedByMonth[monthKey]) { groupedByMonth[monthKey] = [] } groupedByMonth[monthKey].push(photo) }) return Object.entries(groupedByMonth) }, [allPhotos]) ``` **修改 JSX**: 在照片展示部分(第 ~750-800 行) ```typescript // 修改前 {allPhotos.length > 0 ? (
{(() => { const sortedPhotos = [...allPhotos].sort(...) const groupedByMonth: Record = {} // ... 分组逻辑 return Object.entries(groupedByMonth).map(...) })()}
) : ( // ... )} // 修改后 {allPhotos.length > 0 ? (
{groupedPhotosByMonth.map(([month, monthPhotos]) => ( // ... 原有的 JSX ))}
) : ( // ... )} ``` --- #### 步骤 3: 修复 handleAdminPhotoToggle useCallback **文件**: `app/page.tsx` **位置**: 第 ~350 行 ```typescript // 修改前 const handleAdminPhotoToggle = useCallback(async (memberId: string, photoUrl: string, value: boolean) => { if (!isOwner) return const member = treeData.members[memberId] if (!member || !member.photos || member.photos.length === 0) return const key = `${memberId}|${photoUrl}` setAdminToggleLoading(key) try { const updatedPhotos = (member.photos as FamilyPhoto[]).map(photo => photo.url === photoUrl ? { ...photo, adminVisibleOverride: value } : photo ) await updateMember(memberId, { photos: updatedPhotos }) } catch (error) { console.error('更新管理员展示权限失败:', error) } finally { setAdminToggleLoading(null) } }, [isOwner, treeData.members, updateMember]) // 修改后 const handleAdminPhotoToggle = useCallback(async (memberId: string, photoUrl: string, value: boolean) => { if (!isOwner) return const key = `${memberId}|${photoUrl}` setAdminToggleLoading(key) try { const member = treeData.members[memberId] if (!member?.photos?.length) return const updatedPhotos = member.photos.map(photo => photo.url === photoUrl ? { ...photo, adminVisibleOverride: value } : photo ) await updateMember(memberId, { photos: updatedPhotos }) } catch (error) { console.error('更新管理员展示权限失败:', error) } finally { setAdminToggleLoading(null) } }, [isOwner, updateMember]) ``` --- ### 第二阶段:中等优化(预期时间:2-3 小时) #### 步骤 4: 优化 stats 计算为单次遍历 **文件**: `app/page.tsx` **位置**: 第 ~200-250 行 参考 `OPTIMIZATION_EXAMPLES.md` 中的"优化 4"部分,将多次遍历改为单次遍历。 **关键改动**: - 使用单个 `forEach` 循环替代多个 `filter` 和 `map` - 累积计算所有统计数据 - 保持返回值结构不变 **预期性能提升**: 10-15% --- #### 步骤 5: 提取未来三月纪念日计算 **文件**: `app/page.tsx` **位置**: 第 ~600-900 行 参考 `OPTIMIZATION_EXAMPLES.md` 中的"优化 2"部分。 **关键步骤**: 1. 在组件外部定义 `calculateEventDate` 函数 2. 在组件外部定义 `createUpcomingEvent` 函数 3. 创建 `upcomingEvents` useMemo 4. 修改 JSX 使用新的 useMemo **预期性能提升**: 15-25% --- #### 步骤 6: 提取照片卡片为单独组件 **文件**: 新建 `components/dashboard/photo-card.tsx` 参考 `OPTIMIZATION_EXAMPLES.md` 中的"优化 5"部分。 **关键步骤**: 1. 创建新文件 `components/dashboard/photo-card.tsx` 2. 复制 PhotoCard 组件代码 3. 在 `app/page.tsx` 中导入并使用 4. 删除原有的照片卡片 JSX **预期性能提升**: 5-10% --- ### 第三阶段:高级优化(预期时间:4-6 小时) #### 步骤 7: 拆分大型组件 **文件**: 创建多个新文件 **新建文件结构**: ``` components/dashboard/ ├── stats-section.tsx # 统计概览 ├── photos-tab.tsx # 照片标签页 ├── photo-gallery.tsx # 照片库 ├── photo-card.tsx # 照片卡片(已创建) ├── recent-tab.tsx # 动态标签页 ├── anniversaries-section.tsx # 纪念日部分 ├── activity-log-section.tsx # 活动日志部分 ├── statistics-tab.tsx # 统计图表标签页 ├── migration-tab.tsx # 籍贯标签页 └── location-groups.tsx # 籍贯记录 ``` **步骤**: 1. 为每个部分创建单独的组件文件 2. 将相关的 useMemo 和事件处理器移到对应的组件 3. 通过 props 传递必要的数据和回调 4. 在主组件中导入并组合这些子组件 **预期性能提升**: 20-30% --- #### 步骤 8: 实现虚拟滚动(可选) **文件**: `components/dashboard/photo-gallery.tsx` 如果照片数量很多(>100),考虑使用虚拟滚动库: ```bash npm install react-window ``` **实现示例**: ```typescript import { FixedSizeList as List } from 'react-window' const PhotoGallery = ({ groupedPhotos }) => { return ( {({ index, style }) => (
{/* 照片组件 */}
)}
) } ``` **预期性能提升**: 30-50%(仅在照片数量很多时) --- ## 🧪 测试计划 ### 单元测试 ```typescript // tests/page.test.tsx import { render, screen } from '@testing-library/react' import DashboardPage from '@/app/page' describe('DashboardPage Performance', () => { it('should render stats without unnecessary re-renders', () => { const { rerender } = render() // 测试 stats 是否被正确 memoized }) it('should group photos by month efficiently', () => { // 测试照片分组是否被正确 memoized }) it('should calculate upcoming events efficiently', () => { // 测试未来事件计算是否被正确 memoized }) }) ``` ### 性能测试 ```typescript // 使用 React DevTools Profiler // 1. 打开 React DevTools // 2. 切换到 Profiler 标签 // 3. 记录性能数据 // 4. 比较优化前后的性能指标 // 关键指标: // - 组件渲染时间 // - 不必要的重新渲染次数 // - 内存使用量 ``` ### 集成测试 ```typescript // 测试场景: // 1. 初始加载 - 测量首次渲染时间 // 2. 添加成员 - 测量重新渲染时间 // 3. 删除成员 - 测量重新渲染时间 // 4. 更新照片权限 - 测量响应时间 // 5. 切换标签页 - 测量切换时间 ``` --- ## 📊 性能基准 ### 优化前(基准) | 指标 | 值 | |------|-----| | 初始渲染时间 | ~500ms | | 重新渲染时间 | ~300ms | | 内存使用 | ~50MB | | 不必要重新渲染 | 5-10次 | ### 优化后(目标) | 指标 | 值 | 改进 | |------|-----|------| | 初始渲染时间 | ~350ms | -30% | | 重新渲染时间 | ~180ms | -40% | | 内存使用 | ~40MB | -20% | | 不必要重新渲染 | 1-2次 | -80% | --- ## 🔍 验证清单 ### 第一阶段完成后 - [ ] 所有 useMemo 依赖项已修复 - [ ] 照片分组逻辑已 memoized - [ ] handleAdminPhotoToggle useCallback 已修复 - [ ] 没有 TypeScript 错误 - [ ] 功能测试通过 ### 第二阶段完成后 - [ ] stats 计算已优化为单次遍历 - [ ] 未来三月纪念日计算已提取 - [ ] 照片卡片已提取为单独组件 - [ ] 性能提升 10-15% - [ ] 功能测试通过 ### 第三阶段完成后 - [ ] 大型组件已拆分 - [ ] 所有子组件已创建 - [ ] 虚拟滚动已实现(如需要) - [ ] 性能提升 20-30% - [ ] 所有测试通过 --- ## 🚀 部署步骤 1. **创建特性分支** ```bash git checkout -b feat/optimize-dashboard-page ``` 2. **实现优化** - 按照上述步骤逐步实现 - 每个步骤完成后提交一次 3. **测试** ```bash npm run test npm run build ``` 4. **性能测试** - 使用 React DevTools Profiler - 对比优化前后的性能指标 5. **代码审查** - 提交 Pull Request - 等待代码审查 6. **合并和部署** ```bash git merge feat/optimize-dashboard-page npm run deploy ``` --- ## 📚 参考资源 - [React useMemo 文档](https://react.dev/reference/react/useMemo) - [React useCallback 文档](https://react.dev/reference/react/useCallback) - [React DevTools Profiler](https://react.dev/learn/react-developer-tools) - [Web Vitals](https://web.dev/vitals/) - [React 性能优化](https://react.dev/learn/render-and-commit)