Vue3 + TypeScript 组件开发架构规范
🏗️ 整体架构思路
分层架构设计
┌─────────────────────────────────────┐
│ 展示组件层 (Components) │ ← UI渲染 + 用户交互
├─────────────────────────────────────┤
│ 业务逻辑层 (Composable) │ ← 业务逻辑 + 状态处理
├─────────────────────────────────────┤
│ 数据管理层 (Pinia Store) │ ← 全局状态 + 数据持久化
├─────────────────────────────────────┤
│ 类型定义层 (Types) │ ← TypeScript接口定义
└─────────────────────────────────────┘
关注点分离原则
- 组件层: 只关注 UI 渲染和用户交互,保持组件纯粹性
- 逻辑层: 处理业务逻辑、数据转换、副作用管理
- 数据层: 管理全局状态、API调用、数据缓存
- 类型层: 提供完整的 TypeScript 类型支持
📁 目录结构规范
src/
├── components/ # 展示组件层
│ └── reader/
│ ├── toolbar/
│ │ ├── AIToolbar.vue # AI工具栏组件
│ │ ├── ReadingToolbar.vue # 阅读工具栏
│ │ └── index.ts # 组件导出
│ └── content/
│ ├── BookContent.vue # 书籍内容组件
│ └── ChapterNav.vue # 章节导航
├── composables/ # 业务逻辑层
│ ├── useAIToolbar.ts # AI工具栏逻辑
│ ├── useReader.ts # 阅读器核心逻辑
│ ├── usePreviousSummary.ts # 前情提要逻辑
│ └── useBookmark.ts # 书签功能逻辑
├── store/ # 数据管理层
│ ├── modules/
│ │ ├── reader.s.ts # 阅读器状态管理
│ │ ├── ai.s.ts # AI功能状态管理
│ │ └── user.s.ts # 用户状态管理
│ └── index.ts # Store 入口
├── types/ # 类型定义层
│ ├── reader.d.ts # 阅读器相关类型
│ ├── ai.d.ts # AI功能类型
│ └── common.d.ts # 通用类型定义
└── api/ # API接口层
├── reader.ts # 阅读器API
└── ai.ts # AI功能API
🎯 各层职责详解
1. 展示组件层 (Components)
职责: UI渲染、事件处理、用户交互
vue
<!-- components/reader/toolbar/AIToolbar.vue -->
<template>
<div class="ai-toolbar">
<el-button
@click="handleSummaryClick"
:loading="isLoading"
type="primary"
>
前情提要
</el-button>
<el-button @click="handleChatClick">AI对话</el-button>
</div>
</template>
<script setup lang="ts">
import { useAIToolbar } from '@/composables/useAIToolbar'
const {
isLoading,
handleSummaryClick,
handleChatClick
} = useAIToolbar()
</script>
2. 业务逻辑层 (Composables)
职责: 业务逻辑封装、状态管理、副作用处理
typescript
// composables/useAIToolbar.ts
import { ref, computed } from 'vue'
import { useReaderStore } from '@/store/modules/reader'
import { useAIStore } from '@/store/modules/ai'
import type { PreviousSummary } from '@/types/ai'
export function useAIToolbar() {
const readerStore = useReaderStore()
const aiStore = useAIStore()
const isLoading = ref(false)
// 计算属性
const currentBook = computed(() => readerStore.currentBook)
const currentChapter = computed(() => readerStore.currentChapter)
// 前情提要处理
const handleSummaryClick = async () => {
if (!currentBook.value || !currentChapter.value) return
try {
isLoading.value = true
const summary = await aiStore.getPreviousSummary({
bookId: currentBook.value.id,
chapterId: currentChapter.value.id
})
// 显示前情提要弹窗
aiStore.showSummaryDialog(summary)
} catch (error) {
console.error('获取前情提要失败:', error)
} finally {
isLoading.value = false
}
}
// AI对话处理
const handleChatClick = () => {
aiStore.openChatDialog()
}
return {
isLoading,
handleSummaryClick,
handleChatClick
}
}
3. 数据管理层 (Pinia Store)
职责: 全局状态管理、API调用、数据缓存
typescript
// store/modules/ai.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { getPreviousSummaryAPI } from '@/api/ai'
import type { PreviousSummary, AIState } from '@/types/ai'
export const useAIStore = defineStore('ai', () => {
// 状态定义
const summaryCache = ref(new Map<string, PreviousSummary>())
const isSummaryDialogVisible = ref(false)
const currentSummary = ref<PreviousSummary | null>(null)
// 计算属性
const hasCachedSummary = computed(() => (bookId: string, chapterId: string) => {
const key = `${bookId}-${chapterId}`
return summaryCache.value.has(key)
})
// Actions
const getPreviousSummary = async (params: { bookId: string, chapterId: string }) => {
const cacheKey = `${params.bookId}-${params.chapterId}`
// 检查缓存
if (summaryCache.value.has(cacheKey)) {
return summaryCache.value.get(cacheKey)!
}
// API调用
const summary = await getPreviousSummaryAPI(params)
// 缓存结果
summaryCache.value.set(cacheKey, summary)
return summary
}
const showSummaryDialog = (summary: PreviousSummary) => {
currentSummary.value = summary
isSummaryDialogVisible.value = true
}
const closeSummaryDialog = () => {
isSummaryDialogVisible.value = false
currentSummary.value = null
}
return {
// 状态
summaryCache,
isSummaryDialogVisible,
currentSummary,
// 计算属性
hasCachedSummary,
// 方法
getPreviousSummary,
showSummaryDialog,
closeSummaryDialog
}
})
4. 类型定义层 (Types)
职责: TypeScript 类型定义、接口约束
typescript
// types/ai.ts
export interface PreviousSummary {
id: string
bookId: string
chapterId: string
summary: string
keyPoints: string[]
chapterTitle: string
createdAt: string
}
export interface AIDialogState {
isVisible: boolean
messages: ChatMessage[]
isLoading: boolean
}
export interface ChatMessage {
id: string
role: 'user' | 'assistant'
content: string
timestamp: number
}
// API 请求/响应类型
export interface GetSummaryRequest {
bookId: string
chapterId: string
}
export interface GetSummaryResponse {
code: number
message: string
data: PreviousSummary
}
🔄 数据流向示例
用户点击"前情提要"按钮
↓
组件触发事件处理
↓
Composable 处理业务逻辑
↓
Store 检查缓存
↓
(无缓存)调用API获取数据
↓
Store 更新状态并缓存
↓
Composable 更新组件状态
↓
组件重新渲染
📝 命名规范
组件命名
- 大驼峰 + 语义化 :
AIToolbar.vue
,PreviousSummary.vue
- 功能模块前缀 :
Reader*
,AI*
,Book*
Composable 命名
- use 前缀:
useAIToolbar
,useReader
,usePreviousSummary
- 功能描述: 清晰表达composable的主要功能
Store 命名
- 功能模块 :
reader
,ai
,user
,book
- 文件名小写 :
reader.ts
,ai.ts
类型命名
- 接口大驼峰 :
PreviousSummary
,AIDialogState
- 枚举大驼峰 :
LoadingState
,DialogType
✅ 最佳实践
1. 组件纯粹性
vue
<!-- ✅ 好的做法 -->
<script setup lang="ts">
import { useAIToolbar } from '@/composables/useAIToolbar'
const { isLoading, handleClick } = useAIToolbar()
</script>
<!-- ❌ 避免在组件中直接调用API -->
<script setup lang="ts">
import { getPreviousSummaryAPI } from '@/api/ai'
const handleClick = async () => {
const result = await getPreviousSummaryAPI(params) // 不应该在组件中直接调用
}
</script>
2. 状态管理
typescript
// ✅ 在 Store 中管理全局状态
const useAIStore = defineStore('ai', () => {
const globalAIState = ref<AIState>()
return { globalAIState }
})
// ✅ 在 Composable 中管理局部状态
export function useDialog() {
const isVisible = ref(false) // 局部组件状态
return { isVisible }
}
3. 错误处理
typescript
// ✅ 在 Composable 中统一处理错误
export function useAIToolbar() {
const handleSummaryClick = async () => {
try {
await aiStore.getPreviousSummary(params)
} catch (error) {
ElMessage.error('获取前情提要失败')
console.error(error)
}
}
}
4. 类型安全
typescript
// ✅ 完整的类型定义
export function useAIToolbar(): {
isLoading: Ref<boolean>
handleSummaryClick: () => Promise<void>
handleChatClick: () => void
} {
// 实现...
}
🎯 开发流程
- 设计阶段: 先定义 types,明确数据结构
- 数据层: 实现 Store,定义状态管理逻辑
- 逻辑层: 开发 Composable,封装业务逻辑
- 展示层: 创建 Vue 组件,专注UI和交互
- 测试: 分层测试,确保各层职责清晰
通过这种架构,我们能够实现高内聚、低耦合的代码组织,便于维护和扩展。