dramaling-vocab-learning/frontend/store/README.md

8.7 KiB
Raw Permalink Blame History

狀態管理系統文件

📋 概述

這個目錄包含了應用程式的狀態管理系統,採用 Zustand 作為狀態管理工具。系統被設計為模組化架構,將原本單一巨大的 store 拆分為多個專門化的 stores每個都有明確的職責範圍。

🏗️ 架構設計

設計原則

  • 單一職責原則: 每個 store 只負責特定的狀態域
  • 最小重渲染: 組件只訂閱需要的狀態,避免不必要的重渲染
  • 型別安全: 使用 TypeScript 確保型別安全
  • 可測試性: 小型、專注的 stores 更容易測試

Store 分類 (按功能模組組織)

/store/
├── review/                     # 複習功能相關 Store
│   ├── useReviewSessionStore.ts    # 會話狀態管理
│   ├── useTestQueueStore.ts        # 測試隊列管理
│   ├── useTestResultStore.ts       # 測試結果管理
│   ├── useReviewDataStore.ts       # 數據狀態管理
│   └── useReviewUIStore.ts         # Review UI 狀態管理
└── README.md                   # 架構說明文檔

📚 各 Store 詳細說明

1. useReviewSessionStore.ts

職責: 管理複習會話的核心狀態

狀態內容

interface ReviewSessionState {
  // 核心會話狀態
  mounted: boolean              // 組件是否已掛載
  isLoading: boolean           // 是否正在載入
  error: string | null         // 錯誤訊息

  // 當前卡片狀態
  currentCard: ExtendedFlashcard | null  // 當前顯示的詞卡
  currentCardIndex: number               // 當前卡片索引
}

主要功能

  • 會話生命週期管理: 控制會話的開始、結束
  • 當前卡片追蹤: 追蹤使用者正在學習的詞卡
  • 錯誤處理: 統一管理會話相關錯誤

使用範例

const { currentCard, error, setCurrentCard } = useReviewSessionStore()

// 設置當前卡片
setCurrentCard(newCard)

2. useTestQueueStore.ts

職責: 管理測試隊列和測試流程

狀態內容

interface TestQueueState {
  testItems: TestItem[]        // 測試項目清單
  currentTestIndex: number     // 當前測試索引
  completedTests: number       // 已完成測試數量
  totalTests: number          // 總測試數量
  currentMode: ReviewMode     // 當前測試模式
}

主要功能

  • 測試隊列初始化: 根據詞卡和已完成測試建立隊列
  • 測試進度管理: 追蹤測試進度和完成狀態
  • 測試流程控制: 控制測試的前進、跳過等操作

核心方法

// 初始化測試隊列
initializeTestQueue(dueCards, completedTests)

// 進入下一個測試
goToNextTest()

// 跳過當前測試
skipCurrentTest()

// 標記測試完成
markTestCompleted(testIndex)

測試類型

  • flip-memory: 翻卡記憶
  • vocab-choice: 詞彙選擇
  • vocab-listening: 詞彙聽力
  • sentence-listening: 例句聽力
  • sentence-fill: 例句填空
  • sentence-reorder: 例句重組
  • sentence-speaking: 例句口說

3. useTestResultStore.ts

職責: 管理測試結果和分數統計

狀態內容

interface TestResultState {
  score: { correct: number; total: number }  // 分數統計
  isRecordingResult: boolean                 // 是否正在記錄結果
  recordingError: string | null             // 記錄錯誤
}

主要功能

  • 分數追蹤: 記錄正確和總答題數
  • 結果記錄: 將測試結果發送到後端
  • 統計計算: 提供準確率等統計資訊

核心方法

// 更新分數
updateScore(isCorrect: boolean)

// 記錄測試結果到後端
recordTestResult({
  flashcardId,
  testType,
  isCorrect,
  userAnswer,
  confidenceLevel,
  responseTimeMs
})

// 獲取準確率
getAccuracyPercentage()

4. useReviewDataStore.ts

職責: 管理複習數據和UI顯示狀態

狀態內容

interface ReviewDataState {
  dueCards: ExtendedFlashcard[]  // 到期詞卡清單
  showComplete: boolean          // 是否顯示完成畫面
  showNoDueCards: boolean       // 是否顯示無詞卡畫面
  isLoadingCards: boolean       // 是否正在載入詞卡
  loadingError: string | null   // 載入錯誤
}

主要功能

  • 詞卡資料管理: 載入和管理到期的詞卡
  • UI 狀態控制: 控制不同UI狀態的顯示
  • 資料快取: 快取詞卡資料避免重複請求

核心方法

// 載入到期詞卡
loadDueCards()

// 根據ID查找詞卡
findCardById(cardId)

// 獲取詞卡數量
getDueCardsCount()

5. useUIStore.ts

職責: 管理全域UI狀態

狀態內容

interface UIState {
  showTaskListModal: boolean     // 任務清單Modal
  showReportModal: boolean       // 錯誤回報Modal
  modalImage: string | null      // 圖片Modal
  reportReason: string          // 回報原因
  reportingCard: any | null     // 正在回報的詞卡
  isAutoSelecting: boolean      // 自動選擇狀態
}

🔄 Store 之間的協作

資料流向

graph TD
    A[useReviewDataStore] -->|詞卡資料| B[useTestQueueStore]
    B -->|當前測試| C[useReviewSessionStore]
    C -->|測試互動| D[useTestResultStore]
    D -->|結果回饋| B
    E[useUIStore] -.->|UI狀態| A
    E -.->|UI狀態| B
    E -.->|UI狀態| C
    E -.->|UI狀態| D

協作流程

  1. 初始化階段:

    • useReviewDataStore 載入到期詞卡
    • useTestQueueStore 根據詞卡建立測試隊列
    • useReviewSessionStore 設置當前詞卡
  2. 測試階段:

    • useReviewSessionStore 管理當前測試狀態
    • useTestResultStore 記錄測試結果
    • useTestQueueStore 控制測試進度
  3. 完成階段:

    • useTestQueueStore 檢查是否完成所有測試
    • useReviewDataStore 顯示完成狀態

🎯 使用最佳實踐

1. 選擇性訂閱

// ❌ 避免:訂閱整個 store
const store = useReviewSessionStore()

// ✅ 推薦:只訂閱需要的狀態
const { currentCard, error } = useReviewSessionStore()

2. 狀態更新模式

// ✅ 推薦:使用專門的 actions
const { setCurrentCard } = useReviewSessionStore()
setCurrentCard(newCard)

// ❌ 避免:直接修改狀態
// store.currentCard = newCard  // 這樣不會觸發重渲染

3. 錯誤處理

// ✅ 推薦:檢查錯誤狀態
const { error, isLoading } = useReviewSessionStore()

if (error) {
  return <ErrorComponent message={error} />
}

if (isLoading) {
  return <LoadingComponent />
}

🧪 測試策略

單元測試

// 測試 store 的 actions
describe('useTestResultStore', () => {
  it('should update score correctly', () => {
    const { updateScore, score } = useTestResultStore.getState()

    updateScore(true)
    expect(score.correct).toBe(1)
    expect(score.total).toBe(1)
  })
})

整合測試

// 測試多個 stores 的協作
describe('Review Flow Integration', () => {
  it('should coordinate between stores correctly', () => {
    // 測試資料載入 → 隊列建立 → 測試執行的流程
  })
})

🔧 開發工具

Zustand DevTools

import { subscribeWithSelector, devtools } from 'zustand/middleware'

export const useReviewSessionStore = create<ReviewSessionState>()(
  devtools(
    subscribeWithSelector((set, get) => ({
      // store implementation
    })),
    { name: 'review-session-store' }
  )
)

📈 效能考量

重渲染優化

  • 狀態分離: 不相關的狀態變更不會觸發組件重渲染
  • 選擇性訂閱: 組件只訂閱需要的狀態片段
  • 記憶化: 使用 useMemouseCallback 優化計算

記憶體管理

  • 自動清理: stores 會在適當時機重置狀態
  • 垃圾回收: 移除不再需要的資料引用

🚀 未來擴展

新增 Store

  1. 建立新的 store 檔案
  2. 定義 interface 和初始狀態
  3. 實作 actions 和 getters
  4. 加入適當的 TypeScript 型別
  5. 更新文件

Store 拆分指導原則

  • 當 store 超過 150 行時考慮拆分
  • 根據業務邏輯邊界進行拆分
  • 確保拆分後的 stores 職責清晰

📞 支援

如有問題或需要協助,請參考:

維護者: 開發團隊 最後更新: 2025-09-28