dramaling-vocab-learning/ReviewRunner組件詳細說明文檔.md

23 KiB
Raw Blame History

ReviewRunner 組件詳細說明文檔

📋 組件概覽

ReviewRunner 是複習系統的核心容器組件,負責協調 7 種不同的複習模式、管理測驗生命週期、處理答題邏輯,以及控制測驗間的導航流程。

文件位置: /frontend/components/review/ReviewRunner.tsx 組件類型: 容器組件 (Container Component) 職責範圍: 業務邏輯 + 狀態管理 + 組件編排

🏗️ 組件架構設計

架構模式:容器-展示分離

ReviewRunner (容器組件)
├── 狀態管理 (4個 Zustand Store)
├── 業務邏輯 (答題處理、導航控制)
├── 組件編排 (動態渲染7種測驗)
└── 智能導航 (SmartNavigationController)

設計哲學:

  • 容器組件: 處理邏輯和狀態不涉及UI細節
  • 展示組件: 純UI渲染接收 props 和回調
  • 關注點分離: 業務邏輯與UI邏輯完全分離

📊 依賴關係分析

Store 依賴關係

// 4個 Zustand Store 的使用
useReviewSessionStore    // 當前卡片、錯誤狀態
├── currentCard         // 當前複習的詞卡
├── error              // 會話錯誤狀態

useTestQueueStore       // 測驗佇列管理
├── currentMode        // 當前測驗模式
├── testItems          // 測驗項目陣列
├── currentTestIndex   // 當前測驗索引
├── markTestCompleted  // 標記測驗完成
├── goToNextTest       // 切換下一個測驗
└── skipCurrentTest    // 跳過當前測驗

useTestResultStore      // 測驗結果記錄
├── score              // 當前分數統計
├── updateScore        // 更新分數
└── recordTestResult   // 記錄到後端

useReviewUIStore        // UI 狀態管理
├── openReportModal    // 開啟錯誤報告彈窗
└── openImageModal     // 開啟圖片放大彈窗

依賴流程圖:

TestQueueStore (提供測驗項目)
    ↓
ReviewRunner (協調各Store + 渲染組件)
    ↓
TestComponent (處理用戶交互)
    ↓ (答案回調)
ReviewRunner.handleAnswer()
    ↓ (更新狀態)
TestResultStore + TestQueueStore + 後端API

🔄 核心方法詳解

1. handleAnswer - 答題處理核心邏輯

const handleAnswer = useCallback(async (answer: string, confidenceLevel?: number) => {
  // 防護性檢查:避免重複提交或無效狀態下提交
  if (!currentCard || hasAnswered || isProcessingAnswer) return

  setIsProcessingAnswer(true)  // 設置處理中狀態

  try {
    // 第1步答案驗證
    const isCorrect = checkAnswer(answer, currentCard, currentMode)

    // 第2步本地狀態立即更新
    updateScore(isCorrect)

    // 第3步異步同步到後端
    const success = await recordTestResult({
      flashcardId: currentCard.id,
      testType: currentMode,
      isCorrect,
      userAnswer: answer,
      confidenceLevel,
      responseTimeMs: 2000  // 可以改為實際測量值
    })

    // 第4步更新測驗佇列狀態
    if (success) {
      markTestCompleted(currentTestIndex)
      setHasAnswered(true)  // 啟用"繼續"按鈕

      // 第5步答錯處理邏輯 (TODO: 未完全實現)
      if (!isCorrect && currentMode !== 'flip-memory') {
        console.log('答錯,將重新排入隊列')
        // TODO: 實現優先級重排邏輯
      }
    }
  } catch (error) {
    console.error('答題處理失敗:', error)
    // 錯誤處理:可以顯示錯誤提示或重試機制
  } finally {
    setIsProcessingAnswer(false)  // 解除處理中狀態
  }
}, [currentCard, hasAnswered, isProcessingAnswer, currentMode, updateScore, recordTestResult, markTestCompleted, currentTestIndex])

設計特色

  • 防護性檢查: 避免重複提交和無效狀態
  • 樂觀更新: 本地狀態立即更新,異步同步後端
  • 錯誤容錯: 完整的 try-catch 錯誤處理
  • 狀態控制: isProcessingAnswer 防止按鈕重複點擊

2. checkAnswer - 答案驗證邏輯

const checkAnswer = (answer: string, card: any, mode: string): boolean => {
  switch (mode) {
    case 'flip-memory':
      return true // 翻卡記憶沒有對錯,只有信心等級

    case 'vocab-choice':
    case 'vocab-listening':
      return answer === card.word  // 精確匹配詞彙

    case 'sentence-fill':
      return answer.toLowerCase().trim() === card.word.toLowerCase()  // 忽略大小寫

    case 'sentence-reorder':
    case 'sentence-listening':
      return answer.toLowerCase().trim() === card.example.toLowerCase().trim()  // 句子匹配

    case 'sentence-speaking':
      return true // 口說測驗通常算正確 (語音識別待實現)

    default:
      return false
  }
}

演算法特色

  • 模式特化: 每種測驗類型有專門的驗證邏輯
  • 容錯設計: 忽略大小寫和空格
  • 擴展性: 易於添加新的測驗類型驗證

3. generateOptions - 選項生成演算法

const generateOptions = (card: any, mode: string): string[] => {
  switch (mode) {
    case 'vocab-choice':
    case 'vocab-listening':
      // 詞彙選擇生成3個干擾項 + 1個正確答案
      return [card.word, '其他選項1', '其他選項2', '其他選項3']
        .sort(() => Math.random() - 0.5)  // 隨機排序

    case 'sentence-listening':
      // 句子聽力生成3個例句干擾項 + 1個正確例句
      return [
        card.example,
        '其他例句選項1',
        '其他例句選項2',
        '其他例句選項3'
      ].sort(() => Math.random() - 0.5)

    default:
      return []  // 其他模式不需要選項
  }
}

改進空間 (目前為簡化實現):

  • 真實干擾項應基於詞性、CEFR等級、語義相似性生成
  • 需要避免過於簡單或過於困難的干擾項
  • 可考慮用戶歷史錯誤答案作為干擾項

🎛️ 狀態管理流程

本地狀態設計

interface ReviewRunnerState {
  hasAnswered: boolean        // 是否已答題(控制導航按鈕顯示)
  isProcessingAnswer: boolean // 是否正在處理答案(防重複提交)
}

狀態轉換流程

初始狀態: hasAnswered=false, isProcessingAnswer=false
    ↓ (用戶答題)
處理中: hasAnswered=false, isProcessingAnswer=true
    ↓ (處理完成)
已答題: hasAnswered=true, isProcessingAnswer=false
    ↓ (點擊繼續/跳過)
重置狀態: hasAnswered=false, isProcessingAnswer=false (下一題)

生命週期管理

// 測驗切換時自動重置狀態
useEffect(() => {
  setHasAnswered(false)
  setIsProcessingAnswer(false)
}, [currentTestIndex, currentMode])

重置觸發條件

  • currentTestIndex 改變:切換到新測驗
  • currentMode 改變:切換測驗類型
  • 用戶主動跳過或繼續

🎮 動態組件渲染系統

組件映射機制

// 測驗組件映射表
const TEST_COMPONENTS = {
  'flip-memory': FlipMemoryTest,
  'vocab-choice': VocabChoiceTest,
  'sentence-fill': SentenceFillTest,
  'sentence-reorder': SentenceReorderTest,
  'vocab-listening': VocabListeningTest,
  'sentence-listening': SentenceListeningTest,
  'sentence-speaking': SentenceSpeakingTest
} as const

動態渲染邏輯

const renderTestContent = () => {
  // 基於 currentMode 動態選擇組件
  switch (currentMode) {
    case 'flip-memory':
      return (
        <FlipMemoryTest
          {...commonProps}                    // 共通 props
          onConfidenceSubmit={(level) => handleAnswer('', level)}  // 特殊處理
          disabled={isProcessingAnswer}       // 處理中禁用
        />
      )
    // ... 其他模式
  }
}

設計優勢

  • 統一介面: 所有測驗組件使用相同的 commonProps
  • 特化處理: 各測驗的特殊需求通過額外 props 處理
  • 類型安全: TypeScript 確保正確的 props 傳遞

🔀 雙重渲染模式

模式1真實數據模式 (Production)

// 使用真實的 currentCard 數據
if (currentCard) {
  const cardData = {
    id: currentCard.id,
    word: currentCard.word,
    definition: currentCard.definition,
    // ... 完整詞卡數據
  }
  return renderTestContent()  // 渲染真實測驗
}

模式2模擬數據模式 (Development/Testing)

// 當沒有真實數據時,使用 mockFlashcards
if (!currentCard && testItems.length > 0) {
  const currentTest = testItems[currentTestIndex]
  const mockCard = mockFlashcards.find(card => card.id === currentTest.cardId)

  if (mockCard) {
    return renderTestContentWithMockData(mockCard, currentTest.testType, mockOptions)
  }
}

雙重模式的價值

  • 開發便利: 無需後端數據即可測試複習功能
  • 錯誤容錯: 真實數據載入失敗時的降級方案
  • 獨立測試: 前端邏輯可獨立於後端進行測試

🎯 導航控制邏輯

SmartNavigationController 整合

<SmartNavigationController
  hasAnswered={hasAnswered}          // 控制按鈕顯示邏輯
  disabled={isProcessingAnswer}      // 處理中時禁用按鈕
  onSkipCallback={handleSkip}        // 跳過處理函數
  onContinueCallback={handleContinue} // 繼續處理函數
  className="mt-4"
/>

導航邏輯流程

未答題階段: 顯示"跳過"按鈕
    ↓ (用戶答題)
已答題階段: 顯示"繼續"按鈕
    ↓ (用戶點擊繼續)
狀態重置: 準備下一題

跳過和繼續處理

// 跳過邏輯
const handleSkip = useCallback(() => {
  if (hasAnswered) return  // 已答題後不能跳過

  skipCurrentTest()        // 更新 TestQueue Store

  // 重置本地狀態,準備下一題
  setHasAnswered(false)
  setIsProcessingAnswer(false)
}, [hasAnswered, skipCurrentTest])

// 繼續邏輯
const handleContinue = useCallback(() => {
  if (!hasAnswered) return  // 未答題不能繼續

  goToNextTest()           // 切換到下一個測驗

  // 重置本地狀態,準備下一題
  setHasAnswered(false)
  setIsProcessingAnswer(false)
}, [hasAnswered, goToNextTest])

📈 進度追蹤系統

ProgressBar 整合

{/* 進度條顯示邏輯 */}
{testItems.length > 0 && (
  <div className="mb-6">
    <ProgressBar
      current={currentTestIndex}                                    // 當前進度
      total={testItems.length}                                     // 總測驗數
      correct={score.correct}                                      // 正確數量
      incorrect={score.total - score.correct}                     // 錯誤數量
      skipped={testItems.filter(item => item.isSkipped).length}   // 跳過數量
    />
  </div>
)}

進度計算邏輯

  • 完成進度: currentTestIndex / testItems.length * 100
  • 正確率: score.correct / score.total * 100
  • 跳過統計: 實時統計跳過的測驗數量

🧮 測驗組件 Props 設計

統一的 commonProps

const commonProps = {
  cardData,                          // 標準化的卡片數據
  onAnswer: handleAnswer,            // 統一的答題回調
  onReportError: () => openReportModal(currentCard)  // 錯誤報告回調
}

特化的額外 Props

// 翻卡記憶:信心度提交
<FlipMemoryTest
  {...commonProps}
  onConfidenceSubmit={(level) => handleAnswer('', level)}  // 信心度答題
  disabled={isProcessingAnswer}
/>

// 選擇題類型:選項陣列
<VocabChoiceTest
  {...commonProps}
  options={generateOptions(currentCard, currentMode)}      // 動態選項生成
  disabled={isProcessingAnswer}
/>

// 圖片相關測驗:圖片處理
<SentenceReorderTest
  {...commonProps}
  exampleImage={cardData.exampleImage}                     // 圖片數據
  onImageClick={openImageModal}                            // 圖片點擊處理
  disabled={isProcessingAnswer}
/>

🎨 用戶體驗設計

載入和錯誤狀態處理

// 錯誤狀態顯示
if (error) {
  return (
    <div className="text-center py-8">
      <div className="bg-red-50 border border-red-200 rounded-lg p-6">
        <h3 className="text-lg font-semibold text-red-700 mb-2">發生錯誤</h3>
        <p className="text-red-600">{error}</p>
      </div>
    </div>
  )
}

// 載入狀態顯示
if (!currentCard) {
  return (
    <div className="text-center py-8">
      <div className="text-gray-500">載入測驗中...</div>
    </div>
  )
}

UX 設計原則

  • 即時反饋: 用戶操作立即得到視覺回饋
  • 狀態明確: 清晰區分載入、錯誤、正常狀態
  • 防誤操作: 處理中狀態禁用所有交互

視覺層次和佈局

return (
  <div className={`review-runner ${className}`}>
    {/* 第1層進度追蹤 */}
    <div className="mb-6">
      <ProgressBar {...progressProps} />
    </div>

    {/* 第2層測驗內容 (主要區域) */}
    <div className="mb-6">
      {renderTestContent()}
    </div>

    {/* 第3層導航控制 */}
    <div className="border-t pt-6">
      <SmartNavigationController {...navigationProps} />
    </div>
  </div>
)

佈局設計

  • 視覺層次: 進度→內容→導航,符合用戶視線流
  • 間距統一: 使用 mb-6 保持一致間距
  • 分隔線: border-t 明確區分導航區域

性能優化策略

useCallback 優化

// 依賴項精確控制,避免不必要的重新創建
const handleAnswer = useCallback(async (answer: string, confidenceLevel?: number) => {
  // 答題邏輯
}, [currentCard, hasAnswered, isProcessingAnswer, currentMode, updateScore, recordTestResult, markTestCompleted, currentTestIndex])

// 依賴項最小化
const handleSkip = useCallback(() => {
  // 跳過邏輯
}, [hasAnswered, skipCurrentTest])

優化原則

  • 依賴項精確: 只包含實際使用的變數
  • 穩定引用: 避免子組件不必要重渲染
  • 記憶化: 複雜函數使用 useCallback

條件渲染優化

// 避免不必要的組件創建
{testItems.length > 0 && (        // 條件:有測驗項目
  <ProgressBar {...props} />      // 才創建進度條
)}

// 提前返回,減少後續計算
if (error) return <ErrorComponent />
if (!currentCard) return <LoadingComponent />

🔧 技術債務和改進點

當前技術債務

  1. generateOptions 實現簡化
// 當前實現:硬編碼假選項
return [card.word, '其他選項1', '其他選項2', '其他選項3']

// 理想實現:智能干擾項生成
const generateIntelligentDistractors = (correctWord: string, allCards: Card[]): string[] => {
  const samePOS = allCards.filter(c => c.partOfSpeech === correctWord.partOfSpeech)
  const similarCEFR = allCards.filter(c => c.cefr === correctWord.cefr)
  const semanticallySimilar = findSemanticallySimilar(correctWord, allCards)

  return intelligentlySelect(samePOS, similarCEFR, semanticallySimilar, 3)
}
  1. 答錯重排邏輯未完整實現
// TODO 部分:需要實現完整的優先級重排
if (!isCorrect && currentMode !== 'flip-memory') {
  // 當前:只有 console.log
  console.log('答錯,將重新排入隊列')

  // 應該實現:
  const { reorderByPriority, markTestIncorrect } = useTestQueueStore()
  markTestIncorrect(currentTestIndex)
  reorderByPriority()
}
  1. responseTimeMs 測量缺失
// 當前:硬編碼
responseTimeMs: 2000

// 應該實現:實際測量
const [startTime, setStartTime] = useState<number>()
useEffect(() => {
  setStartTime(Date.now())  // 測驗開始時記錄
}, [currentTestIndex])

const actualResponseTime = Date.now() - (startTime || 0)

建議的改進方向

1. 智能干擾項生成系統

interface DistractorGenerationEngine {
  // 基於詞性的干擾項
  generateByPartOfSpeech(word: string, pos: string): string[]

  // 基於CEFR等級的干擾項
  generateByCEFRLevel(word: string, level: string): string[]

  // 基於語義相似性的干擾項
  generateBySemantics(word: string): string[]

  // 基於用戶歷史錯誤的干擾項
  generateByUserMistakes(word: string, userHistory: ErrorHistory[]): string[]
}

2. 完整的答題分析系統

interface AnswerAnalyticsEngine {
  // 答題時間分析
  analyzeResponseTime(startTime: number, endTime: number): ResponseMetrics

  // 答錯模式分析
  categorizeError(
    userAnswer: string,
    correctAnswer: string,
    testType: ReviewMode
  ): ErrorCategory

  // 學習建議生成
  generateLearningAdvice(
    errorPattern: ErrorPattern,
    userProfile: UserProfile
  ): LearningAdvice[]
}

3. 自適應難度調整

interface AdaptiveDifficultyEngine {
  // 動態調整測驗難度
  adjustDifficulty(
    currentPerformance: PerformanceMetrics,
    userProfile: UserProfile
  ): DifficultyAdjustment

  // 個性化測驗序列
  optimizeTestSequence(
    remainingTests: TestItem[],
    userStrongWeakPoints: UserAnalytics
  ): TestItem[]
}

📊 性能指標和監控

關鍵性能指標

渲染性能

  • 組件切換時間: 目標 <300ms
  • 答題處理時間: 目標 <500ms
  • 狀態更新延遲: 目標 <100ms

記憶體使用

  • 組件記憶體: 每個測驗組件 <5MB
  • 狀態記憶體: 整體 Store 狀態 <10MB
  • 清理機制: 組件卸載時自動清理

網路性能

  • 答題同步: 目標 <1秒
  • 佇列載入: 目標 <2秒
  • 錯誤重試: 自動重試 3 次

性能監控實現

// 可添加的性能監控邏輯
const usePerformanceMonitoring = () => {
  const startTime = useRef<number>()

  useEffect(() => {
    startTime.current = performance.now()
  }, [currentTestIndex])

  const recordMetrics = useCallback((action: string) => {
    if (startTime.current) {
      const duration = performance.now() - startTime.current
      console.log(`${action} took ${duration.toFixed(2)}ms`)

      // 可以發送到分析服務
      analytics.track('test_performance', {
        action,
        duration,
        testType: currentMode,
        cardId: currentCard?.id
      })
    }
  }, [currentMode, currentCard])

  return { recordMetrics }
}

🔮 未來擴展可能性

1. 實時協作學習

interface CollaborativeLearning {
  // 多人同時複習
  joinSession(sessionId: string): Promise<CollaborativeSession>

  // 實時同步進度
  syncProgress(progress: ProgressState): Promise<void>

  // 互助提示系統
  requestHint(testId: string): Promise<PeerHint>
  provideHint(testId: string, hint: string): Promise<void>
}

2. AI輔助學習

interface AIAssistedLearning {
  // 智能提示系統
  generateHint(
    testType: ReviewMode,
    cardData: ReviewCardData,
    userAttempts: Attempt[]
  ): LearningHint

  // 個性化難度
  adjustDifficulty(
    userPerformance: PerformanceHistory,
    targetAccuracy: number
  ): DifficultyParams

  // 學習路徑優化
  optimizeLearningPath(
    userWeaknesses: WeaknessProfile,
    availableTime: number
  ): OptimizedPath
}

3. 多模態學習整合

interface MultimodalLearning {
  // VR/AR 學習環境
  enterVRMode(testType: ReviewMode): Promise<VRSession>

  // 語音評估整合
  enableSpeechAssessment(): Promise<SpeechEvaluator>

  // 手寫識別
  enableHandwritingRecognition(): Promise<HandwritingEngine>

  // 眼動追蹤學習分析
  trackLearningAttention(): Promise<AttentionMetrics>
}

🏆 組件設計優勢

架構優勢

  1. 模組化設計: 清晰的職責分離,易於維護和擴展
  2. 類型安全: 完整的 TypeScript 類型定義,編譯時錯誤檢查
  3. 狀態管理: Zustand 提供高效的跨組件狀態同步
  4. 性能優化: useCallback 和條件渲染減少不必要的重新渲染
  5. 錯誤處理: 完整的錯誤邊界和降級方案

開發體驗優勢

  1. 開發效率: 模擬數據模式支援獨立開發
  2. 測試友好: 純函數設計便於單元測試
  3. 調試便利: 詳細的 console.log 和錯誤訊息
  4. 擴展性: 新測驗類型可透過 switch case 輕易添加

學習體驗優勢

  1. 即時反饋: 答題結果立即顯示
  2. 進度可視: 詳細的進度追蹤和統計
  3. 智能導航: 根據答題狀態智能顯示操作選項
  4. 容錯機制: 跳過和重試機制避免學習中斷

🔧 使用指南和最佳實踐

組件使用方式

// 在頁面中使用 ReviewRunner
import { ReviewRunner } from '@/components/review/ReviewRunner'

const ReviewPage = () => {
  return (
    <div className="review-page">
      <Navigation />
      <div className="max-w-4xl mx-auto px-4 py-8">
        <ReviewRunner className="review-content" />
      </div>
    </div>
  )
}

自定義測驗類型擴展

// 1. 創建新的測驗組件
const NewTestType = ({ cardData, onAnswer, disabled }) => {
  // 測驗邏輯實現
  return <div>新測驗類型UI</div>
}

// 2. 在 ReviewRunner 中添加映射
case 'new-test-type':
  return (
    <NewTestType
      {...commonProps}
      disabled={isProcessingAnswer}
      // 特化 props
    />
  )

// 3. 更新類型定義
export type ReviewMode =
  | 'flip-memory'
  | 'vocab-choice'
  | 'new-test-type'  // 新增

Store 狀態訂閱最佳實踐

// 精確訂閱,避免不必要重渲染
const currentTest = useTestQueueStore(state =>
  state.testItems[state.currentTestIndex]
)

// 避免:訂閱整個 Store
const store = useTestQueueStore()  // ❌ 會導致所有變化都重渲染

// 推薦:選擇性訂閱
const { currentMode, currentTestIndex } = useTestQueueStore(state => ({
  currentMode: state.currentMode,
  currentTestIndex: state.currentTestIndex
}))  // ✅ 只有這兩個屬性變化才重渲染

📋 總結

ReviewRunner 是複習系統的核心控制中樞,展現了現代 React 應用的最佳實踐:

  1. 容器-展示分離: 邏輯與UI完全分離
  2. 狀態管理: 多Store協作職責分明
  3. 動態渲染: 基於狀態的智能組件切換
  4. 用戶體驗: 完整的錯誤處理和載入狀態
  5. 性能優化: useCallback和條件渲染優化
  6. 可擴展性: 新測驗類型易於添加

這個組件是複習功能架構設計的精華,體現了複雜業務邏輯的優雅實現


文檔版本: v1.0 分析對象: ReviewRunner.tsx (440行) 最後更新: 2025-10-02