827 lines
23 KiB
Markdown
827 lines
23 KiB
Markdown
# ReviewRunner 組件詳細說明文檔
|
||
|
||
## 📋 組件概覽
|
||
|
||
`ReviewRunner` 是複習系統的**核心容器組件**,負責協調 7 種不同的複習模式、管理測驗生命週期、處理答題邏輯,以及控制測驗間的導航流程。
|
||
|
||
**文件位置**: `/frontend/components/review/ReviewRunner.tsx`
|
||
**組件類型**: 容器組件 (Container Component)
|
||
**職責範圍**: 業務邏輯 + 狀態管理 + 組件編排
|
||
|
||
## 🏗️ 組件架構設計
|
||
|
||
### 架構模式:容器-展示分離
|
||
|
||
```
|
||
ReviewRunner (容器組件)
|
||
├── 狀態管理 (4個 Zustand Store)
|
||
├── 業務邏輯 (答題處理、導航控制)
|
||
├── 組件編排 (動態渲染7種測驗)
|
||
└── 智能導航 (SmartNavigationController)
|
||
```
|
||
|
||
**設計哲學**:
|
||
- **容器組件**: 處理邏輯和狀態,不涉及UI細節
|
||
- **展示組件**: 純UI渲染,接收 props 和回調
|
||
- **關注點分離**: 業務邏輯與UI邏輯完全分離
|
||
|
||
## 📊 依賴關係分析
|
||
|
||
### Store 依賴關係
|
||
|
||
```typescript
|
||
// 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 - 答題處理核心邏輯
|
||
|
||
```typescript
|
||
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 - 答案驗證邏輯
|
||
|
||
```typescript
|
||
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 - 選項生成演算法
|
||
|
||
```typescript
|
||
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等級、語義相似性生成
|
||
- 需要避免過於簡單或過於困難的干擾項
|
||
- 可考慮用戶歷史錯誤答案作為干擾項
|
||
|
||
## 🎛️ 狀態管理流程
|
||
|
||
### 本地狀態設計
|
||
|
||
```typescript
|
||
interface ReviewRunnerState {
|
||
hasAnswered: boolean // 是否已答題(控制導航按鈕顯示)
|
||
isProcessingAnswer: boolean // 是否正在處理答案(防重複提交)
|
||
}
|
||
```
|
||
|
||
**狀態轉換流程**:
|
||
```
|
||
初始狀態: hasAnswered=false, isProcessingAnswer=false
|
||
↓ (用戶答題)
|
||
處理中: hasAnswered=false, isProcessingAnswer=true
|
||
↓ (處理完成)
|
||
已答題: hasAnswered=true, isProcessingAnswer=false
|
||
↓ (點擊繼續/跳過)
|
||
重置狀態: hasAnswered=false, isProcessingAnswer=false (下一題)
|
||
```
|
||
|
||
### 生命週期管理
|
||
|
||
```typescript
|
||
// 測驗切換時自動重置狀態
|
||
useEffect(() => {
|
||
setHasAnswered(false)
|
||
setIsProcessingAnswer(false)
|
||
}, [currentTestIndex, currentMode])
|
||
```
|
||
|
||
**重置觸發條件**:
|
||
- `currentTestIndex` 改變:切換到新測驗
|
||
- `currentMode` 改變:切換測驗類型
|
||
- 用戶主動跳過或繼續
|
||
|
||
## 🎮 動態組件渲染系統
|
||
|
||
### 組件映射機制
|
||
|
||
```typescript
|
||
// 測驗組件映射表
|
||
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
|
||
```
|
||
|
||
**動態渲染邏輯**:
|
||
```typescript
|
||
const renderTestContent = () => {
|
||
// 基於 currentMode 動態選擇組件
|
||
switch (currentMode) {
|
||
case 'flip-memory':
|
||
return (
|
||
<FlipMemoryTest
|
||
{...commonProps} // 共通 props
|
||
onConfidenceSubmit={(level) => handleAnswer('', level)} // 特殊處理
|
||
disabled={isProcessingAnswer} // 處理中禁用
|
||
/>
|
||
)
|
||
// ... 其他模式
|
||
}
|
||
}
|
||
```
|
||
|
||
**設計優勢**:
|
||
- **統一介面**: 所有測驗組件使用相同的 `commonProps`
|
||
- **特化處理**: 各測驗的特殊需求通過額外 props 處理
|
||
- **類型安全**: TypeScript 確保正確的 props 傳遞
|
||
|
||
## 🔀 雙重渲染模式
|
||
|
||
### 模式1:真實數據模式 (Production)
|
||
|
||
```typescript
|
||
// 使用真實的 currentCard 數據
|
||
if (currentCard) {
|
||
const cardData = {
|
||
id: currentCard.id,
|
||
word: currentCard.word,
|
||
definition: currentCard.definition,
|
||
// ... 完整詞卡數據
|
||
}
|
||
return renderTestContent() // 渲染真實測驗
|
||
}
|
||
```
|
||
|
||
### 模式2:模擬數據模式 (Development/Testing)
|
||
|
||
```typescript
|
||
// 當沒有真實數據時,使用 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 整合
|
||
|
||
```typescript
|
||
<SmartNavigationController
|
||
hasAnswered={hasAnswered} // 控制按鈕顯示邏輯
|
||
disabled={isProcessingAnswer} // 處理中時禁用按鈕
|
||
onSkipCallback={handleSkip} // 跳過處理函數
|
||
onContinueCallback={handleContinue} // 繼續處理函數
|
||
className="mt-4"
|
||
/>
|
||
```
|
||
|
||
**導航邏輯流程**:
|
||
```
|
||
未答題階段: 顯示"跳過"按鈕
|
||
↓ (用戶答題)
|
||
已答題階段: 顯示"繼續"按鈕
|
||
↓ (用戶點擊繼續)
|
||
狀態重置: 準備下一題
|
||
```
|
||
|
||
### 跳過和繼續處理
|
||
|
||
```typescript
|
||
// 跳過邏輯
|
||
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 整合
|
||
|
||
```typescript
|
||
{/* 進度條顯示邏輯 */}
|
||
{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
|
||
|
||
```typescript
|
||
const commonProps = {
|
||
cardData, // 標準化的卡片數據
|
||
onAnswer: handleAnswer, // 統一的答題回調
|
||
onReportError: () => openReportModal(currentCard) // 錯誤報告回調
|
||
}
|
||
```
|
||
|
||
### 特化的額外 Props
|
||
|
||
```typescript
|
||
// 翻卡記憶:信心度提交
|
||
<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}
|
||
/>
|
||
```
|
||
|
||
## 🎨 用戶體驗設計
|
||
|
||
### 載入和錯誤狀態處理
|
||
|
||
```typescript
|
||
// 錯誤狀態顯示
|
||
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 設計原則**:
|
||
- **即時反饋**: 用戶操作立即得到視覺回饋
|
||
- **狀態明確**: 清晰區分載入、錯誤、正常狀態
|
||
- **防誤操作**: 處理中狀態禁用所有交互
|
||
|
||
### 視覺層次和佈局
|
||
|
||
```typescript
|
||
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 優化
|
||
|
||
```typescript
|
||
// 依賴項精確控制,避免不必要的重新創建
|
||
const handleAnswer = useCallback(async (answer: string, confidenceLevel?: number) => {
|
||
// 答題邏輯
|
||
}, [currentCard, hasAnswered, isProcessingAnswer, currentMode, updateScore, recordTestResult, markTestCompleted, currentTestIndex])
|
||
|
||
// 依賴項最小化
|
||
const handleSkip = useCallback(() => {
|
||
// 跳過邏輯
|
||
}, [hasAnswered, skipCurrentTest])
|
||
```
|
||
|
||
**優化原則**:
|
||
- **依賴項精確**: 只包含實際使用的變數
|
||
- **穩定引用**: 避免子組件不必要重渲染
|
||
- **記憶化**: 複雜函數使用 useCallback
|
||
|
||
### 條件渲染優化
|
||
|
||
```typescript
|
||
// 避免不必要的組件創建
|
||
{testItems.length > 0 && ( // 條件:有測驗項目
|
||
<ProgressBar {...props} /> // 才創建進度條
|
||
)}
|
||
|
||
// 提前返回,減少後續計算
|
||
if (error) return <ErrorComponent />
|
||
if (!currentCard) return <LoadingComponent />
|
||
```
|
||
|
||
## 🔧 技術債務和改進點
|
||
|
||
### 當前技術債務
|
||
|
||
1. **generateOptions 實現簡化**:
|
||
```typescript
|
||
// 當前實現:硬編碼假選項
|
||
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)
|
||
}
|
||
```
|
||
|
||
2. **答錯重排邏輯未完整實現**:
|
||
```typescript
|
||
// TODO 部分:需要實現完整的優先級重排
|
||
if (!isCorrect && currentMode !== 'flip-memory') {
|
||
// 當前:只有 console.log
|
||
console.log('答錯,將重新排入隊列')
|
||
|
||
// 應該實現:
|
||
const { reorderByPriority, markTestIncorrect } = useTestQueueStore()
|
||
markTestIncorrect(currentTestIndex)
|
||
reorderByPriority()
|
||
}
|
||
```
|
||
|
||
3. **responseTimeMs 測量缺失**:
|
||
```typescript
|
||
// 當前:硬編碼
|
||
responseTimeMs: 2000
|
||
|
||
// 應該實現:實際測量
|
||
const [startTime, setStartTime] = useState<number>()
|
||
useEffect(() => {
|
||
setStartTime(Date.now()) // 測驗開始時記錄
|
||
}, [currentTestIndex])
|
||
|
||
const actualResponseTime = Date.now() - (startTime || 0)
|
||
```
|
||
|
||
### 建議的改進方向
|
||
|
||
#### 1. 智能干擾項生成系統
|
||
|
||
```typescript
|
||
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. 完整的答題分析系統
|
||
|
||
```typescript
|
||
interface AnswerAnalyticsEngine {
|
||
// 答題時間分析
|
||
analyzeResponseTime(startTime: number, endTime: number): ResponseMetrics
|
||
|
||
// 答錯模式分析
|
||
categorizeError(
|
||
userAnswer: string,
|
||
correctAnswer: string,
|
||
testType: ReviewMode
|
||
): ErrorCategory
|
||
|
||
// 學習建議生成
|
||
generateLearningAdvice(
|
||
errorPattern: ErrorPattern,
|
||
userProfile: UserProfile
|
||
): LearningAdvice[]
|
||
}
|
||
```
|
||
|
||
#### 3. 自適應難度調整
|
||
|
||
```typescript
|
||
interface AdaptiveDifficultyEngine {
|
||
// 動態調整測驗難度
|
||
adjustDifficulty(
|
||
currentPerformance: PerformanceMetrics,
|
||
userProfile: UserProfile
|
||
): DifficultyAdjustment
|
||
|
||
// 個性化測驗序列
|
||
optimizeTestSequence(
|
||
remainingTests: TestItem[],
|
||
userStrongWeakPoints: UserAnalytics
|
||
): TestItem[]
|
||
}
|
||
```
|
||
|
||
## 📊 性能指標和監控
|
||
|
||
### 關鍵性能指標
|
||
|
||
**渲染性能**:
|
||
- **組件切換時間**: 目標 <300ms
|
||
- **答題處理時間**: 目標 <500ms
|
||
- **狀態更新延遲**: 目標 <100ms
|
||
|
||
**記憶體使用**:
|
||
- **組件記憶體**: 每個測驗組件 <5MB
|
||
- **狀態記憶體**: 整體 Store 狀態 <10MB
|
||
- **清理機制**: 組件卸載時自動清理
|
||
|
||
**網路性能**:
|
||
- **答題同步**: 目標 <1秒
|
||
- **佇列載入**: 目標 <2秒
|
||
- **錯誤重試**: 自動重試 3 次
|
||
|
||
### 性能監控實現
|
||
|
||
```typescript
|
||
// 可添加的性能監控邏輯
|
||
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. 實時協作學習
|
||
|
||
```typescript
|
||
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輔助學習
|
||
|
||
```typescript
|
||
interface AIAssistedLearning {
|
||
// 智能提示系統
|
||
generateHint(
|
||
testType: ReviewMode,
|
||
cardData: ReviewCardData,
|
||
userAttempts: Attempt[]
|
||
): LearningHint
|
||
|
||
// 個性化難度
|
||
adjustDifficulty(
|
||
userPerformance: PerformanceHistory,
|
||
targetAccuracy: number
|
||
): DifficultyParams
|
||
|
||
// 學習路徑優化
|
||
optimizeLearningPath(
|
||
userWeaknesses: WeaknessProfile,
|
||
availableTime: number
|
||
): OptimizedPath
|
||
}
|
||
```
|
||
|
||
### 3. 多模態學習整合
|
||
|
||
```typescript
|
||
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. **容錯機制**: 跳過和重試機制避免學習中斷
|
||
|
||
## 🔧 使用指南和最佳實踐
|
||
|
||
### 組件使用方式
|
||
|
||
```typescript
|
||
// 在頁面中使用 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>
|
||
)
|
||
}
|
||
```
|
||
|
||
### 自定義測驗類型擴展
|
||
|
||
```typescript
|
||
// 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 狀態訂閱最佳實踐
|
||
|
||
```typescript
|
||
// 精確訂閱,避免不必要重渲染
|
||
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* |