334 lines
8.7 KiB
Markdown
334 lines
8.7 KiB
Markdown
# 狀態管理系統文件
|
||
|
||
## 📋 概述
|
||
|
||
這個目錄包含了應用程式的狀態管理系統,採用 **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
|
||
**職責**: 管理複習會話的核心狀態
|
||
|
||
#### 狀態內容
|
||
```typescript
|
||
interface ReviewSessionState {
|
||
// 核心會話狀態
|
||
mounted: boolean // 組件是否已掛載
|
||
isLoading: boolean // 是否正在載入
|
||
error: string | null // 錯誤訊息
|
||
|
||
// 當前卡片狀態
|
||
currentCard: ExtendedFlashcard | null // 當前顯示的詞卡
|
||
currentCardIndex: number // 當前卡片索引
|
||
}
|
||
```
|
||
|
||
#### 主要功能
|
||
- **會話生命週期管理**: 控制會話的開始、結束
|
||
- **當前卡片追蹤**: 追蹤使用者正在學習的詞卡
|
||
- **錯誤處理**: 統一管理會話相關錯誤
|
||
|
||
#### 使用範例
|
||
```typescript
|
||
const { currentCard, error, setCurrentCard } = useReviewSessionStore()
|
||
|
||
// 設置當前卡片
|
||
setCurrentCard(newCard)
|
||
```
|
||
|
||
---
|
||
|
||
### 2. useTestQueueStore.ts
|
||
**職責**: 管理測試隊列和測試流程
|
||
|
||
#### 狀態內容
|
||
```typescript
|
||
interface TestQueueState {
|
||
testItems: TestItem[] // 測試項目清單
|
||
currentTestIndex: number // 當前測試索引
|
||
completedTests: number // 已完成測試數量
|
||
totalTests: number // 總測試數量
|
||
currentMode: ReviewMode // 當前測試模式
|
||
}
|
||
```
|
||
|
||
#### 主要功能
|
||
- **測試隊列初始化**: 根據詞卡和已完成測試建立隊列
|
||
- **測試進度管理**: 追蹤測試進度和完成狀態
|
||
- **測試流程控制**: 控制測試的前進、跳過等操作
|
||
|
||
#### 核心方法
|
||
```typescript
|
||
// 初始化測試隊列
|
||
initializeTestQueue(dueCards, completedTests)
|
||
|
||
// 進入下一個測試
|
||
goToNextTest()
|
||
|
||
// 跳過當前測試
|
||
skipCurrentTest()
|
||
|
||
// 標記測試完成
|
||
markTestCompleted(testIndex)
|
||
```
|
||
|
||
#### 測試類型
|
||
- `flip-memory`: 翻卡記憶
|
||
- `vocab-choice`: 詞彙選擇
|
||
- `vocab-listening`: 詞彙聽力
|
||
- `sentence-listening`: 例句聽力
|
||
- `sentence-fill`: 例句填空
|
||
- `sentence-reorder`: 例句重組
|
||
- `sentence-speaking`: 例句口說
|
||
|
||
---
|
||
|
||
### 3. useTestResultStore.ts
|
||
**職責**: 管理測試結果和分數統計
|
||
|
||
#### 狀態內容
|
||
```typescript
|
||
interface TestResultState {
|
||
score: { correct: number; total: number } // 分數統計
|
||
isRecordingResult: boolean // 是否正在記錄結果
|
||
recordingError: string | null // 記錄錯誤
|
||
}
|
||
```
|
||
|
||
#### 主要功能
|
||
- **分數追蹤**: 記錄正確和總答題數
|
||
- **結果記錄**: 將測試結果發送到後端
|
||
- **統計計算**: 提供準確率等統計資訊
|
||
|
||
#### 核心方法
|
||
```typescript
|
||
// 更新分數
|
||
updateScore(isCorrect: boolean)
|
||
|
||
// 記錄測試結果到後端
|
||
recordTestResult({
|
||
flashcardId,
|
||
testType,
|
||
isCorrect,
|
||
userAnswer,
|
||
confidenceLevel,
|
||
responseTimeMs
|
||
})
|
||
|
||
// 獲取準確率
|
||
getAccuracyPercentage()
|
||
```
|
||
|
||
---
|
||
|
||
### 4. useReviewDataStore.ts
|
||
**職責**: 管理複習數據和UI顯示狀態
|
||
|
||
#### 狀態內容
|
||
```typescript
|
||
interface ReviewDataState {
|
||
dueCards: ExtendedFlashcard[] // 到期詞卡清單
|
||
showComplete: boolean // 是否顯示完成畫面
|
||
showNoDueCards: boolean // 是否顯示無詞卡畫面
|
||
isLoadingCards: boolean // 是否正在載入詞卡
|
||
loadingError: string | null // 載入錯誤
|
||
}
|
||
```
|
||
|
||
#### 主要功能
|
||
- **詞卡資料管理**: 載入和管理到期的詞卡
|
||
- **UI 狀態控制**: 控制不同UI狀態的顯示
|
||
- **資料快取**: 快取詞卡資料避免重複請求
|
||
|
||
#### 核心方法
|
||
```typescript
|
||
// 載入到期詞卡
|
||
loadDueCards()
|
||
|
||
// 根據ID查找詞卡
|
||
findCardById(cardId)
|
||
|
||
// 獲取詞卡數量
|
||
getDueCardsCount()
|
||
```
|
||
|
||
---
|
||
|
||
### 5. useUIStore.ts
|
||
**職責**: 管理全域UI狀態
|
||
|
||
#### 狀態內容
|
||
```typescript
|
||
interface UIState {
|
||
showTaskListModal: boolean // 任務清單Modal
|
||
showReportModal: boolean // 錯誤回報Modal
|
||
modalImage: string | null // 圖片Modal
|
||
reportReason: string // 回報原因
|
||
reportingCard: any | null // 正在回報的詞卡
|
||
isAutoSelecting: boolean // 自動選擇狀態
|
||
}
|
||
```
|
||
|
||
## 🔄 Store 之間的協作
|
||
|
||
### 資料流向
|
||
```mermaid
|
||
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. 選擇性訂閱
|
||
```typescript
|
||
// ❌ 避免:訂閱整個 store
|
||
const store = useReviewSessionStore()
|
||
|
||
// ✅ 推薦:只訂閱需要的狀態
|
||
const { currentCard, error } = useReviewSessionStore()
|
||
```
|
||
|
||
### 2. 狀態更新模式
|
||
```typescript
|
||
// ✅ 推薦:使用專門的 actions
|
||
const { setCurrentCard } = useReviewSessionStore()
|
||
setCurrentCard(newCard)
|
||
|
||
// ❌ 避免:直接修改狀態
|
||
// store.currentCard = newCard // 這樣不會觸發重渲染
|
||
```
|
||
|
||
### 3. 錯誤處理
|
||
```typescript
|
||
// ✅ 推薦:檢查錯誤狀態
|
||
const { error, isLoading } = useReviewSessionStore()
|
||
|
||
if (error) {
|
||
return <ErrorComponent message={error} />
|
||
}
|
||
|
||
if (isLoading) {
|
||
return <LoadingComponent />
|
||
}
|
||
```
|
||
|
||
## 🧪 測試策略
|
||
|
||
### 單元測試
|
||
```typescript
|
||
// 測試 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)
|
||
})
|
||
})
|
||
```
|
||
|
||
### 整合測試
|
||
```typescript
|
||
// 測試多個 stores 的協作
|
||
describe('Review Flow Integration', () => {
|
||
it('should coordinate between stores correctly', () => {
|
||
// 測試資料載入 → 隊列建立 → 測試執行的流程
|
||
})
|
||
})
|
||
```
|
||
|
||
## 🔧 開發工具
|
||
|
||
### Zustand DevTools
|
||
```typescript
|
||
import { subscribeWithSelector, devtools } from 'zustand/middleware'
|
||
|
||
export const useReviewSessionStore = create<ReviewSessionState>()(
|
||
devtools(
|
||
subscribeWithSelector((set, get) => ({
|
||
// store implementation
|
||
})),
|
||
{ name: 'review-session-store' }
|
||
)
|
||
)
|
||
```
|
||
|
||
## 📈 效能考量
|
||
|
||
### 重渲染優化
|
||
- **狀態分離**: 不相關的狀態變更不會觸發組件重渲染
|
||
- **選擇性訂閱**: 組件只訂閱需要的狀態片段
|
||
- **記憶化**: 使用 `useMemo` 和 `useCallback` 優化計算
|
||
|
||
### 記憶體管理
|
||
- **自動清理**: stores 會在適當時機重置狀態
|
||
- **垃圾回收**: 移除不再需要的資料引用
|
||
|
||
## 🚀 未來擴展
|
||
|
||
### 新增 Store
|
||
1. 建立新的 store 檔案
|
||
2. 定義 interface 和初始狀態
|
||
3. 實作 actions 和 getters
|
||
4. 加入適當的 TypeScript 型別
|
||
5. 更新文件
|
||
|
||
### Store 拆分指導原則
|
||
- 當 store 超過 150 行時考慮拆分
|
||
- 根據業務邏輯邊界進行拆分
|
||
- 確保拆分後的 stores 職責清晰
|
||
|
||
---
|
||
|
||
## 📞 支援
|
||
|
||
如有問題或需要協助,請參考:
|
||
- [Zustand 官方文件](https://zustand-demo.pmnd.rs/)
|
||
- [TypeScript 最佳實踐](https://www.typescriptlang.org/docs/)
|
||
- 團隊內部技術文件
|
||
|
||
**維護者**: 開發團隊
|
||
**最後更新**: 2025-09-28 |