diff --git a/DramaLing前端系統架構報告.md b/DramaLing前端系統架構報告.md new file mode 100644 index 0000000..271bf0e --- /dev/null +++ b/DramaLing前端系統架構報告.md @@ -0,0 +1,488 @@ +# DramaLing 前端系統架構分析報告 + +## 📋 專案概覽 + +**技術棧**: +- **框架**:Next.js 15.5.3 (App Router) +- **語言**:TypeScript 5.9.2 +- **狀態管理**:Zustand 5.0.8 +- **樣式**:Tailwind CSS 3.4.17 +- **圖標**:Lucide React +- **部署**:Zeabur + +**專案規模**: +- 約 150+ 個組件和模組文件 +- 8 個主要路由頁面 +- 5 個 Zustand Store +- 多層級的服務和工具函數架構 + +## 🏗️ 系統架構層次 + +### 1. 目錄結構分析 + +``` +frontend/ +├── app/ # Next.js App Router 頁面 +│ ├── flashcards/ # 詞卡管理頁面 +│ ├── generate/ # AI 生成頁面 +│ ├── review/ # 複習系統頁面 +│ ├── review-design/ # 複習設計頁面 +│ └── settings/ # 設定頁面 +├── components/ # React 組件庫 +│ ├── shared/ # 共享基礎組件 (12個) +│ ├── flashcards/ # 詞卡功能組件 (10個) +│ ├── review/ # 複習系統組件 (20+個) +│ ├── generate/ # AI生成組件 (5個) +│ ├── ui/ # 基礎UI組件 (1個) +│ ├── word/ # 詞彙分析組件 (3個) +│ └── media/ # 媒體組件 (已重構) +├── hooks/ # 自定義 Hook 層 +│ ├── flashcards/ # 詞卡邏輯 (7個) +│ ├── review/ # 複習邏輯 (2個) +│ ├── generate/ # 生成邏輯 (2個) +│ └── shared/ # 共享邏輯 (1個) +├── store/ # Zustand 狀態管理 +│ ├── useReviewSessionStore.ts # 複習會話 +│ ├── useTestQueueStore.ts # 測試佇列 +│ ├── useReviewDataStore.ts # 複習數據 +│ ├── useTestResultStore.ts # 測試結果 +│ └── useUIStore.ts # UI狀態 +├── lib/ # 核心服務層 +│ ├── services/ # API 服務 +│ ├── utils/ # 工具函數 +│ ├── types/ # 類型定義 +│ └── config/ # 配置管理 +└── types/ # 全域類型定義 +``` + +## 🎯 架構設計模式分析 + +### 2.1 分層架構模式 (Layered Architecture) + +``` +┌─────────────────┐ +│ 表現層 (UI) │ ← React 組件 +├─────────────────┤ +│ 業務邏輯層 │ ← Custom Hooks +├─────────────────┤ +│ 狀態管理層 │ ← Zustand Stores +├─────────────────┤ +│ 服務層 (API) │ ← API Services +└─────────────────┘ +``` + +**設計優勢**: +- ✅ 職責分離清晰 +- ✅ 層次間耦合度低 +- ✅ 易於測試和維護 +- ✅ 支援並行開發 + +### 2.2 Hooks + Store 雙層模式 + +**Hook 層**(業務邏輯封裝): +- 包裝 Store 操作 + API 呼叫 +- 提供組件友好的介面 +- 處理複雜的業務邏輯 + +**Store 層**(純狀態管理): +- Zustand 全域狀態 +- 跨組件狀態同步 +- 最小重渲染優化 + +## 📊 組件架構分析 + +### 3.1 共享組件庫 (components/shared/) + +**基礎組件** (12個): +- `BluePlayButton` - 統一播放按鈕 🆕 +- `ContentBlock` - 內容區塊 +- `ErrorState` - 錯誤狀態顯示 +- `LoadingState` - 載入狀態 +- `Modal` - 彈窗組件 +- `Navigation` - 導航列 +- `PaginationControls` - 分頁控制 +- `ProtectedRoute` - 路由保護 +- `StatisticsCard` - 統計卡片 +- `TabNavigation` - 標籤導航 +- `Toast` - 通知系統 +- `ValidatedTextInput` - 驗證輸入框 + +**設計評價**:⭐⭐⭐⭐⭐ +- 組件職責單一,重用性高 +- API 設計一致,易於使用 +- 支援主題化和客製化 + +### 3.2 功能專用組件 + +**詞卡組件** (10個): +- 完整的 CRUD 操作組件 +- 圖片生成和管理組件 +- 搜尋和篩選組件 + +**複習組件** (20+個): +- 7 種複習模式的測試組件 +- 統一的測試結果顯示 +- 進度追蹤組件 + +**AI生成組件** (5個): +- 句子分析組件 +- 詞彙選擇組件 +- 慣用語展示組件 + +## 🔄 狀態管理分析 + +### 4.1 Zustand Store 設計 + +**架構優勢**: +- 使用 `subscribeWithSelector` 中間件優化性能 +- Store 按功能域分離,避免巨大單體 +- 狀態更新邏輯集中化 + +**Store 職責劃分**: + +1. **useReviewSessionStore** - 複習會話核心狀態 + - 當前詞卡、進度、模式設定 + - 會話生命週期管理 + +2. **useTestQueueStore** - 測試佇列邏輯 + - 智能優先級算法 + - 測試順序管理 + +3. **useReviewDataStore** - 複習資料管理 + - 待複習詞卡載入 + - 數據快取機制 + +4. **useTestResultStore** - 測試結果追蹤 + - 答題結果記錄 + - 統計數據計算 + +5. **useUIStore** - UI 狀態管理 + - 全域 UI 狀態 + - 主題和設定 + +### 4.2 Hook 封裝層 + +**設計模式**:Store 包裝器 + 業務邏輯 + +```typescript +// 範例:useReviewSession Hook +export const useReviewSession = () => { + const store = useReviewSessionStore() + + // 業務邏輯:載入下一張卡片 + const loadNextCard = async () => { + try { + store.setLoading(true) + const result = await flashcardsService.getDueFlashcards() + store.setDueCards(result.data) + } catch (error) { + store.setError(error.message) + } finally { + store.setLoading(false) + } + } + + return { ...store, loadNextCard } +} +``` + +## 🛠️ 服務層架構 + +### 5.1 API 服務設計 + +**統一 API 架構**: +```typescript +// lib/services/flashcards.ts +class FlashcardsService { + private baseURL = 'http://localhost:5008' + + // 統一的請求處理 + private async makeRequest(endpoint: string): Promise> + + // 業務方法 + async getFlashcards(): Promise> + async createFlashcard(data: CreateFlashcardRequest): Promise> +} +``` + +**優勢**: +- 統一的錯誤處理機制 +- 類型安全的 API 介面 +- 支援開發/生產環境切換 + +### 5.2 工具函數層 + +**核心工具**: +- `cefrUtils.ts` - CEFR 等級處理 +- `flashcardUtils.ts` - 詞卡工具函數 +- `testUtils.ts` - 測試相關工具 + +## 📈 性能分析 + +### 6.1 現況評估 + +**優勢**: +- ✅ 使用 Next.js App Router 的最新優化 +- ✅ Tailwind CSS 的 purge 機制 +- ✅ TypeScript 的編譯時優化 + +**問題識別**: +- ❌ 缺乏組件記憶化 (React.memo) +- ❌ 大型組件未進行代碼分割 +- ❌ 圖片資源未優化 (WebP, 響應式) +- ❌ 無 Service Worker 快取機制 + +### 6.2 Bundle 分析 (推估) + +**目前 Bundle 大小**: +- **主應用**:~2.5MB (開發模式) +- **複習模組**:~800KB +- **AI生成模組**:~400KB +- **詞卡模組**:~600KB + +## 🔧 具體優化建議 + +### 7.1 立即可實施 (本週) + +1. **組件記憶化**: + ```typescript + // 高頻重渲染組件加上 React.memo + export const FlashcardCard = React.memo(FlashcardCard) + export const VocabularyStatsGrid = React.memo(VocabularyStatsGrid) + ``` + +2. **圖片優化**: + ```typescript + // 添加圖片懶載入 + ... + ``` + +3. **Hook 合併**: + ```typescript + // 合併相似的 TTS 播放邏輯 + const useTTSManager = () => { + // 統一的 TTS 邏輯 + } + ``` + +### 7.2 短期優化 (2-4週) + +1. **代碼分割**: + ```typescript + // 路由級別的懶載入 + const ReviewPage = lazy(() => import('./review/page')) + const GeneratePage = lazy(() => import('./generate/page')) + ``` + +2. **Store 重構**: + ```typescript + // 拆分大型 Store + const useTestQueueCore = create(...) // 核心邏輯 + const useTestQueueUI = create(...) // UI 狀態 + ``` + +3. **API 快取**: + ```typescript + // 實施 SWR 或 React Query + const { data, error, mutate } = useSWR('/api/flashcards', fetcher) + ``` + +### 7.3 中期重構 (1-2月) + +1. **微前端模組化**: + ```typescript + // 按功能拆分獨立模組 + apps/ + ├── flashcards-module/ + ├── review-module/ + ├── generate-module/ + └── shell-app/ + ``` + +2. **設計系統**: + ```typescript + // 建立完整的設計系統 + components/ + ├── atoms/ # 原子組件 + ├── molecules/ # 分子組件 + ├── organisms/ # 有機體組件 + └── templates/ # 頁面模板 + ``` + +3. **測試體系**: + ```typescript + // 完整的測試覆蓋 + __tests__/ + ├── components/ # 組件測試 + ├── hooks/ # Hook 測試 + ├── stores/ # Store 測試 + └── e2e/ # E2E 測試 + ``` + +## 🎯 架構成熟度評分 + +| 分類 | 評分 | 說明 | +|-----|-----|------| +| **組件設計** | ⭐⭐⭐⭐⭐ | 模組化程度高,重用性良好 | +| **狀態管理** | ⭐⭐⭐⭐ | 分層清晰但複雜度稍高 | +| **類型安全** | ⭐⭐⭐⭐⭐ | TypeScript 覆蓋率完整 | +| **性能優化** | ⭐⭐⭐ | 基礎優化到位,進階優化待加強 | +| **測試覆蓋** | ⭐⭐ | 缺乏完整的測試體系 | +| **文檔完整** | ⭐⭐⭐ | 基礎文檔存在,詳細文檔不足 | +| **可維護性** | ⭐⭐⭐⭐ | 架構清晰,但複雜度需控制 | + +**整體評分**:⭐⭐⭐⭐ (4/5) - **優秀的現代前端架構** + +## 🔍 深度分析:雙層設計模式 + +### Hook + Store 雙層設計詳解 + +你提到的重點:**為什麼要 Hook 和 Store 兩層?** + +**實際案例分析**: + +#### Store 層 (純狀態) +```typescript +// useReviewSessionStore.ts +{ + currentCard: ExtendedFlashcard | null, + isLoading: boolean, + setCurrentCard: (card) => set({ currentCard: card }), + setLoading: (loading) => set({ isLoading: loading }) +} +``` + +#### Hook 層 (業務邏輯) +```typescript +// useReviewSession.ts +export const useReviewSession = () => { + const store = useReviewSessionStore() + + const loadNextCard = async () => { + store.setLoading(true) // Store 操作 + const result = await flashcardsService.getDueFlashcards() // API 呼叫 + store.setCurrentCard(result.data[0]) // Store 操作 + store.setLoading(false) // Store 操作 + } + + return { ...store, loadNextCard } // Store + 業務邏輯 +} +``` + +### 📊 雙層設計的價值 + +**優點**: +1. **關注點分離**: Store 專注狀態,Hook 專注邏輯 +2. **重用性**: Store 可被多個 Hook 使用 +3. **可測試性**: 可獨立測試狀態邏輯和業務邏輯 +4. **擴展性**: 新功能容易添加新 Hook + +**缺點**: +1. **學習成本**: 開發者需理解兩層概念 +2. **複雜度**: 簡單功能可能過度工程 +3. **調試難度**: 狀態流向更複雜 + +### 🤔 是否過度設計? + +**分析**: + +**適合雙層的場景**: +- ✅ 複習系統:複雜狀態 + 複雜邏輯 +- ✅ 詞卡管理:多種操作模式 +- ✅ AI 生成:異步處理 + 狀態追蹤 + +**可能過度的場景**: +- ❓ 簡單的 UI 狀態管理 +- ❓ 一次性業務邏輯 +- ❓ 純工具函數封裝 + +## 🚨 發現的架構問題 + +### 問題 1: Hook 邏輯重複 +**現象**:多個組件都有相似的 TTS 播放邏輯 +**影響**:代碼重複,維護成本高 +**解決**:✅ 已通過 `BluePlayButton` 組件統一 + +### 問題 2: Store 複雜度過高 +**現象**:`useTestQueueStore` 有 394 行代碼 +**影響**:難以理解和維護 +**建議**:拆分為多個小 Store + +### 問題 3: 類型定義分散 +**現象**:類型定義在多個地方重複 +**影響**:類型不一致,重構困難 +**建議**:建立統一的類型註冊中心 + +### 問題 4: 缺乏性能優化 +**現象**:沒有 React.memo, lazy loading +**影響**:不必要的重渲染 +**建議**:實施組件記憶化策略 + +## 🎯 優化路線圖 + +### Phase 1: 立即優化 (1週) +- [ ] 組件記憶化 (React.memo) +- [ ] 圖片懶載入 +- [ ] 統一 TTS 邏輯 ✅ + +### Phase 2: 短期優化 (2-4週) +- [ ] Store 拆分重構 +- [ ] 代碼分割實施 +- [ ] API 快取機制 + +### Phase 3: 中期重構 (1-2月) +- [ ] 設計系統建立 +- [ ] 測試框架完整化 +- [ ] 性能監控整合 + +### Phase 4: 長期規劃 (2-3月) +- [ ] 微前端架構 +- [ ] PWA 功能 +- [ ] 國際化支援 + +## 💡 關鍵建議 + +### 建議 1: 簡化狀態管理 +**當前**:Hook + Store 雙層 +**建議**:評估是否需要簡化為單層(對於簡單狀態) + +### 建議 2: 實施性能優化 +**優先級**:🔴 高 +- 添加 React.memo 到高頻組件 +- 實施路由級代碼分割 +- 圖片和資源優化 + +### 建議 3: 建立測試體系 +**優先級**:🟡 中 +- 單元測試 (Jest + RTL) +- 整合測試 +- E2E 測試 (Playwright) + +### 建議 4: 文檔完善 +**優先級**:🟡 中 +- Storybook 組件文檔 +- API 文檔 +- 架構決策記錄 (ADR) + +## 📋 總結 + +DramaLing 前端架構**整體優秀**,採用現代的分層設計模式,組件化程度高,類型安全性強。 + +**核心優勢**: +- 清晰的模組分離 +- 良好的開發體驗 +- 強大的功能完整性 + +**改進空間**: +- 性能優化需要加強 +- 複雜度控制需要平衡 +- 測試和文檔需要完善 + +**推薦策略**: +採用**漸進式重構**,優先解決性能問題,然後逐步優化架構複雜度,建立完整的測試和文檔體系。 + +--- + +*報告生成時間: 2025-10-01* +*分析範圍: Frontend 系統完整架構* +*建議更新頻率: 每月一次* \ No newline at end of file diff --git a/frontend/app/review/page.tsx b/frontend/app/review/page.tsx index 3b2f65d..2d445e1 100644 --- a/frontend/app/review/page.tsx +++ b/frontend/app/review/page.tsx @@ -13,11 +13,11 @@ import { LoadingStates } from '@/components/review/LoadingStates' import { ReviewRunner } from '@/components/review/ReviewRunner' // 狀態管理 -import { useReviewSessionStore } from '@/store/useReviewSessionStore' -import { useTestQueueStore } from '@/store/useTestQueueStore' -import { useTestResultStore } from '@/store/useTestResultStore' -import { useReviewDataStore } from '@/store/useReviewDataStore' -import { useUIStore } from '@/store/useUIStore' +import { useReviewSessionStore } from '@/store/review/useReviewSessionStore' +import { useTestQueueStore } from '@/store/review/useTestQueueStore' +import { useTestResultStore } from '@/store/review/useTestResultStore' +import { useReviewDataStore } from '@/store/review/useReviewDataStore' +import { useReviewUIStore } from '@/store/review/useReviewUIStore' import { ReviewService } from '@/lib/services/review/reviewService' export default function LearnPage() { @@ -53,7 +53,7 @@ export default function LearnPage() { closeReportModal, closeImageModal, setReportReason - } = useUIStore() + } = useReviewUIStore() // 初始化 useEffect(() => { diff --git a/frontend/components/debug/TestDebugPanel.tsx b/frontend/components/debug/TestDebugPanel.tsx index 2e940f4..7e34546 100644 --- a/frontend/components/debug/TestDebugPanel.tsx +++ b/frontend/components/debug/TestDebugPanel.tsx @@ -1,6 +1,6 @@ import React, { useState } from 'react' -import { useTestQueueStore } from '@/store/useTestQueueStore' -import { useTestResultStore } from '@/store/useTestResultStore' +import { useTestQueueStore } from '@/store/review/useTestQueueStore' +import { useTestResultStore } from '@/store/review/useTestResultStore' import { mockFlashcards, getTestStatistics, generateTestQueue } from '@/data/mockTestData' interface TestDebugPanelProps { diff --git a/frontend/components/review/NavigationController.tsx b/frontend/components/review/NavigationController.tsx index 6d272ae..3b886d9 100644 --- a/frontend/components/review/NavigationController.tsx +++ b/frontend/components/review/NavigationController.tsx @@ -1,5 +1,5 @@ import React, { memo, useCallback, useMemo } from 'react' -import { useTestQueueStore } from '@/store/useTestQueueStore' +import { useTestQueueStore } from '@/store/review/useTestQueueStore' /** * 智能導航控制器 diff --git a/frontend/components/review/ReviewRunner.tsx b/frontend/components/review/ReviewRunner.tsx index c237465..9962be6 100644 --- a/frontend/components/review/ReviewRunner.tsx +++ b/frontend/components/review/ReviewRunner.tsx @@ -1,8 +1,8 @@ import { useEffect, useState, useCallback } from 'react' -import { useReviewSessionStore } from '@/store/useReviewSessionStore' -import { useTestQueueStore } from '@/store/useTestQueueStore' -import { useTestResultStore } from '@/store/useTestResultStore' -import { useUIStore } from '@/store/useUIStore' +import { useReviewSessionStore } from '@/store/review/useReviewSessionStore' +import { useTestQueueStore } from '@/store/review/useTestQueueStore' +import { useTestResultStore } from '@/store/review/useTestResultStore' +import { useReviewUIStore } from '@/store/review/useReviewUIStore' import { SmartNavigationController } from './NavigationController' import { ProgressBar } from './ProgressBar' import { mockFlashcards } from '@/data/mockTestData' @@ -32,7 +32,7 @@ export const ReviewRunner: React.FC = ({ className }) => { const { openReportModal, openImageModal - } = useUIStore() + } = useReviewUIStore() // 重置答題狀態(切換測驗時) useEffect(() => { diff --git a/frontend/components/review/TaskListModal.tsx b/frontend/components/review/TaskListModal.tsx index 1ae9cea..68bdd20 100644 --- a/frontend/components/review/TaskListModal.tsx +++ b/frontend/components/review/TaskListModal.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { TestItem } from '@/store/useTestQueueStore' +import { TestItem } from '@/store/review/useTestQueueStore' import { TestStatusIndicator, TestStats, TestProgressBar, TestStatusList } from './TestStatusIndicator' interface TaskListModalProps { diff --git a/frontend/components/review/TestStatusIndicator.tsx b/frontend/components/review/TestStatusIndicator.tsx index 987c3d4..3b2bff6 100644 --- a/frontend/components/review/TestStatusIndicator.tsx +++ b/frontend/components/review/TestStatusIndicator.tsx @@ -1,5 +1,5 @@ import React, { memo } from 'react' -import { TestItem } from '@/store/useTestQueueStore' +import { TestItem } from '@/store/review/useTestQueueStore' /** * 測驗狀態指示器 diff --git a/frontend/hooks/review/useReviewSession.ts b/frontend/hooks/review/useReviewSession.ts index 6879581..5bd307d 100644 --- a/frontend/hooks/review/useReviewSession.ts +++ b/frontend/hooks/review/useReviewSession.ts @@ -3,7 +3,7 @@ * 提供便捷的 Store 訪問方式,但所有邏輯都統一在 Store 中 */ -import { useReviewSessionStore } from '@/store/useReviewSessionStore' +import { useReviewSessionStore } from '@/store/review/useReviewSessionStore' import { flashcardsService } from '@/lib/services/flashcards' import type { ExtendedFlashcard, ReviewMode } from '@/lib/types/review' diff --git a/frontend/lib/services/review/reviewService.ts b/frontend/lib/services/review/reviewService.ts index 958c65b..37a6d5d 100644 --- a/frontend/lib/services/review/reviewService.ts +++ b/frontend/lib/services/review/reviewService.ts @@ -1,6 +1,6 @@ import { flashcardsService } from '@/lib/services/flashcards' import { ExtendedFlashcard } from '@/lib/types/review' -import { TestItem } from '@/store/useTestQueueStore' +import { TestItem } from '@/store/review/useTestQueueStore' // 複習會話服務 export class ReviewService { diff --git a/frontend/store/README.md b/frontend/store/README.md index 149ba10..185e8e6 100644 --- a/frontend/store/README.md +++ b/frontend/store/README.md @@ -12,14 +12,16 @@ - **型別安全**: 使用 TypeScript 確保型別安全 - **可測試性**: 小型、專注的 stores 更容易測試 -### Store 分類 +### Store 分類 (按功能模組組織) ``` /store/ -├── useReviewSessionStore.ts # 會話狀態管理 -├── useTestQueueStore.ts # 測試隊列管理 -├── useTestResultStore.ts # 測試結果管理 -├── useReviewDataStore.ts # 數據狀態管理 -└── useUIStore.ts # UI 狀態管理 +├── review/ # 複習功能相關 Store +│ ├── useReviewSessionStore.ts # 會話狀態管理 +│ ├── useTestQueueStore.ts # 測試隊列管理 +│ ├── useTestResultStore.ts # 測試結果管理 +│ ├── useReviewDataStore.ts # 數據狀態管理 +│ └── useReviewUIStore.ts # Review UI 狀態管理 +└── README.md # 架構說明文檔 ``` ## 📚 各 Store 詳細說明 diff --git a/frontend/store/useReviewDataStore.ts b/frontend/store/review/useReviewDataStore.ts similarity index 100% rename from frontend/store/useReviewDataStore.ts rename to frontend/store/review/useReviewDataStore.ts diff --git a/frontend/store/useReviewSessionStore.ts b/frontend/store/review/useReviewSessionStore.ts similarity index 100% rename from frontend/store/useReviewSessionStore.ts rename to frontend/store/review/useReviewSessionStore.ts diff --git a/frontend/store/useUIStore.ts b/frontend/store/review/useReviewUIStore.ts similarity index 89% rename from frontend/store/useUIStore.ts rename to frontend/store/review/useReviewUIStore.ts index 392de99..07691fb 100644 --- a/frontend/store/useUIStore.ts +++ b/frontend/store/review/useReviewUIStore.ts @@ -1,7 +1,7 @@ import { create } from 'zustand' -// UI 狀態管理 -interface UIState { +// Review UI 狀態管理 +interface ReviewUIState { // Modal 狀態 showTaskListModal: boolean showReportModal: boolean @@ -29,7 +29,7 @@ interface UIState { closeImageModal: () => void } -export const useUIStore = create((set) => ({ +export const useReviewUIStore = create((set) => ({ // 初始狀態 showTaskListModal: false, showReportModal: false, diff --git a/frontend/store/useTestQueueStore.ts b/frontend/store/review/useTestQueueStore.ts similarity index 100% rename from frontend/store/useTestQueueStore.ts rename to frontend/store/review/useTestQueueStore.ts diff --git a/frontend/store/useTestResultStore.ts b/frontend/store/review/useTestResultStore.ts similarity index 100% rename from frontend/store/useTestResultStore.ts rename to frontend/store/review/useTestResultStore.ts