All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 54s
Docker Build & Deploy / Deploy to Production (push) Successful in 9s
Docker Build & Deploy / Cleanup Dangling Images (push) Successful in 1s
Docker Build & Deploy / WeChat Notification (push) Successful in 2s
326 lines
9.1 KiB
Markdown
326 lines
9.1 KiB
Markdown
|
|
## Vue 3 Composition API Research - Modular Architecture Best Practices
|
|
|
|
### 研究日期: 2026-02-03
|
|
|
|
---
|
|
|
|
## 1. 官方 Vue 3 组件组织原则
|
|
|
|
### 1.1 Composables 用于代码组织
|
|
来源: Vue 官方文档 - https://vuejs.org/guide/reusability/composables
|
|
|
|
**核心原则:**
|
|
- Composables 不仅用于复用,也用于**代码组织**
|
|
- 当组件变得过于复杂时,应该将逻辑按**关注点分离**提取到更小的函数中
|
|
- 可以将提取的 composables 视为**组件级别的服务**,它们可以相互通信
|
|
|
|
**官方示例模式:**
|
|
```vue
|
|
<script setup>
|
|
import { useFeatureA } from './featureA.js'
|
|
import { useFeatureB } from './featureB.js'
|
|
import { useFeatureC } from './featureC.js'
|
|
|
|
const { foo, bar } = useFeatureA()
|
|
const { baz } = useFeatureB(foo)
|
|
const { qux } = useFeatureC(baz)
|
|
</script>
|
|
```
|
|
|
|
**关键洞察:**
|
|
- Composables 应返回**普通对象**包含多个 refs,保持响应式
|
|
- 避免返回 reactive 对象,因为解构会失去响应性
|
|
- Composables 可以接收其他 composables 的返回值作为参数
|
|
|
|
---
|
|
|
|
## 2. 代码分割与懒加载
|
|
|
|
### 2.1 defineAsyncComponent 用于模块懒加载
|
|
来源: Vue 官方文档 - https://github.com/vuejs/docs/blob/main/src/guide/best-practices/performance.md
|
|
|
|
**适用场景:**
|
|
- 将大型组件树分割成独立的 chunks
|
|
- 仅在组件渲染时才加载,改善初始加载时间
|
|
|
|
```js
|
|
import { defineAsyncComponent } from 'vue'
|
|
|
|
// Foo.vue 及其依赖被单独打包成一个 chunk
|
|
// 只有在组件被渲染时才会按需获取
|
|
const Foo = defineAsyncComponent(() => import('./Foo.vue'))
|
|
```
|
|
|
|
### 2.2 动态导入用于 JS 代码分割
|
|
```js
|
|
// lazy.js 及其依赖会被分割成单独的 chunk
|
|
// 只在 loadLazy() 被调用时才加载
|
|
function loadLazy() {
|
|
return import('./lazy.js')
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 3. 真实世界的模块化架构模式
|
|
|
|
### 3.1 Dashboard 模块化架构 - 成功案例
|
|
|
|
**案例 1: Soybean Admin (MIT License)**
|
|
来源: https://github.com/soybeanjs/soybean-admin/blob/main/src/views/home/index.vue
|
|
|
|
```vue
|
|
<script setup lang="ts">
|
|
import { computed } from 'vue';
|
|
import { useAppStore } from '@/store/modules/app';
|
|
import HeaderBanner from './modules/header-banner.vue';
|
|
import CardData from './modules/card-data.vue';
|
|
import LineChart from './modules/line-chart.vue';
|
|
import PieChart from './modules/pie-chart.vue';
|
|
import ProjectNews from './modules/project-news.vue';
|
|
import CreativityBanner from './modules/creativity-banner.vue';
|
|
|
|
const appStore = useAppStore();
|
|
const gap = computed(() => (appStore.isMobile ? 0 : 16));
|
|
</script>
|
|
```
|
|
|
|
**架构特点:**
|
|
- Index.vue 作为**容器组件**,只负责布局和响应式计算
|
|
- 每个 modules/*.vue 是**独立的功能模块**
|
|
- 模块命名清晰: header-banner, card-data, line-chart 等
|
|
- 使用 Pinia store 进行状态共享
|
|
|
|
**案例 2: Art Design Pro (MIT License)**
|
|
来源: https://github.com/Daymychen/art-design-pro/blob/main/src/views/dashboard/ecommerce/index.vue
|
|
|
|
```vue
|
|
<script setup lang="ts">
|
|
import Banner from './modules/banner.vue'
|
|
import TotalOrderVolume from './modules/total-order-volume.vue'
|
|
import TotalProducts from './modules/total-products.vue'
|
|
import SalesTrend from './modules/sales-trend.vue'
|
|
import SalesClassification from './modules/sales-classification.vue'
|
|
import TransactionList from './modules/transaction-list.vue'
|
|
import HotCommodity from './modules/hot-commodity.vue'
|
|
import RecentTransaction from './modules/recent-transaction.vue'
|
|
import AnnualSales from './modules/annual-sales.vue'
|
|
import ProductSales from './modules/product-sales.vue'
|
|
import SalesGrowth from './modules/sales-growth.vue'
|
|
import CartConversionRate from './modules/cart-conversion-rate.vue'
|
|
import HotProductsList from './modules/hot-products-list.vue'
|
|
|
|
defineOptions({ name: 'Ecommerce' })
|
|
</script>
|
|
```
|
|
|
|
**架构特点:**
|
|
- 电商 dashboard 包含 13 个独立模块
|
|
- 每个模块代表一个业务功能卡片
|
|
- Index.vue **不传递数据**,模块自治
|
|
|
|
---
|
|
|
|
## 4. 模块间通信模式
|
|
|
|
### 4.1 defineEmits 用于子到父通信
|
|
来源: Vue 核心仓库 - https://github.com/vuejs/core/blob/main/packages/runtime-core/src/apiSetupHelpers.ts
|
|
|
|
**TypeScript 类型声明模式:**
|
|
```ts
|
|
const emit = defineEmits<{
|
|
'update:modelValue': [value: string];
|
|
'change': [event: Event];
|
|
'custom-event': [payload: CustomPayload];
|
|
}>();
|
|
```
|
|
|
|
**Runtime 声明模式:**
|
|
```js
|
|
const emit = defineEmits(['change', 'update'])
|
|
```
|
|
|
|
### 4.2 Props 模式 - 数据传递 vs 自取数据
|
|
|
|
**案例研究: Halo CMS (GPL-3.0)**
|
|
来源: https://github.com/halo-dev/halo/blob/main/ui/console-src/modules/system/users/components/GrantPermissionModal.vue
|
|
|
|
```vue
|
|
<script setup lang="ts">
|
|
import { computed, onMounted, ref } from "vue";
|
|
import { useI18n } from "vue-i18n";
|
|
import { useFetchRoles, useFetchRoleTemplates } from "../composables/use-role";
|
|
|
|
const props = withDefaults(
|
|
defineProps<{
|
|
user?: User;
|
|
}>(),
|
|
{
|
|
user: undefined,
|
|
}
|
|
);
|
|
|
|
const emit = defineEmits<{
|
|
(event: "close"): void;
|
|
}>();
|
|
|
|
// 模块自己获取数据
|
|
onMounted(async () => {
|
|
await fetchRoles();
|
|
});
|
|
</script>
|
|
```
|
|
|
|
**模式总结:**
|
|
- **Props 传递身份标识** (如 user ID),而非完整数据
|
|
- **模块自己获取详细数据** (通过 composables)
|
|
- 这样保持模块的**高内聚低耦合**
|
|
|
|
---
|
|
|
|
## 5. 何时模块应该自取数据 vs 接收 Props
|
|
|
|
### 5.1 自取数据的场景
|
|
- 模块是**独立的业务单元**(如日历、统计卡片)
|
|
- 数据获取逻辑属于模块内部关注点
|
|
- 模块需要**定期刷新**或**重新加载**数据
|
|
- 多个平行模块各自管理自己的状态
|
|
|
|
**示例:**
|
|
```vue
|
|
<!-- 统计卡片模块 - 自己获取数据 -->
|
|
<script setup lang="ts">
|
|
const { data, loading } = useBudgetStats()
|
|
|
|
onMounted(() => {
|
|
loadStats()
|
|
})
|
|
</script>
|
|
```
|
|
|
|
### 5.2 接收 Props 的场景
|
|
- 模块是**展示组件**(Presentational Component)
|
|
- 父组件需要**协调多个子组件**的数据
|
|
- 数据来源于**全局状态管理**(如 Pinia store)
|
|
- 需要在父组件层面做**数据聚合或转换**
|
|
|
|
**示例:**
|
|
```vue
|
|
<!-- 数据展示组件 - 接收 props -->
|
|
<script setup lang="ts">
|
|
const props = defineProps<{
|
|
stats: BudgetStats
|
|
loading: boolean
|
|
}>()
|
|
</script>
|
|
```
|
|
|
|
---
|
|
|
|
## 6. TypeScript vs JavaScript 在 Vue 3 项目中
|
|
|
|
### 6.1 EmailBill 项目的选择
|
|
**当前状况:**
|
|
- ESLint 配置中禁用了 TypeScript 规则
|
|
- 使用 `<script setup lang="ts">` 但不强制类型检查
|
|
- 轻量级类型提示,不追求严格类型安全
|
|
|
|
**何时避免 TypeScript:**
|
|
- 小型项目,团队更熟悉 JavaScript
|
|
- 快速原型开发
|
|
- 避免 TypeScript 配置和类型定义的复杂度
|
|
- 保持构建速度和开发体验的流畅
|
|
|
|
**何时使用 TypeScript:**
|
|
- 大型团队协作
|
|
- 复杂的状态管理和数据流
|
|
- 需要严格的 API 契约
|
|
- 长期维护的企业级应用
|
|
|
|
---
|
|
|
|
## 7. 模块化架构的最佳实践总结
|
|
|
|
### 7.1 目录结构推荐
|
|
```
|
|
views/
|
|
calendar/
|
|
Index.vue # 容器组件,布局和协调
|
|
modules/
|
|
CalendarView.vue # 日历展示模块(自取数据)
|
|
MonthlyStats.vue # 月度统计模块(自取数据)
|
|
QuickActions.vue # 快捷操作模块(事件驱动)
|
|
composables/
|
|
useCalendarData.ts # 日历数据获取逻辑
|
|
useMonthlyStats.ts # 统计数据获取逻辑
|
|
```
|
|
|
|
### 7.2 组件职责划分
|
|
|
|
**Index.vue (容器组件):**
|
|
- 布局管理和响应式设计
|
|
- 协调模块间的通信(如果需要)
|
|
- 全局状态初始化
|
|
- **不应包含业务逻辑**
|
|
|
|
**modules/*.vue (功能模块):**
|
|
- 独立的业务功能单元
|
|
- 自己管理数据获取和状态
|
|
- 通过 emits 向父组件通信
|
|
- 高内聚,低耦合
|
|
|
|
**composables/*.ts (可复用逻辑):**
|
|
- 数据获取逻辑
|
|
- 业务规则计算
|
|
- 状态管理辅助
|
|
- 可在多个组件间共享
|
|
|
|
### 7.3 通信模式推荐
|
|
|
|
**模块向上通信 (Child → Parent):**
|
|
```ts
|
|
const emit = defineEmits<{
|
|
'date-changed': [date: Date]
|
|
'item-clicked': [item: CalendarItem]
|
|
}>()
|
|
```
|
|
|
|
**模块间通信 (Sibling ↔ Sibling):**
|
|
- 通过**父组件中转**事件
|
|
- 或使用**全局事件总线**(如 mitt)
|
|
- 或使用**Pinia store** 共享状态
|
|
|
|
---
|
|
|
|
## 8. 关键洞察和建议
|
|
|
|
### 8.1 高内聚模块设计
|
|
- 每个模块应该是**自治的**,包含自己的数据获取、状态管理和事件处理
|
|
- Index.vue 应该是**轻量级的协调者**,而非数据的中央枢纽
|
|
|
|
### 8.2 Props vs 自取数据的平衡
|
|
- **身份标识和配置通过 props** (如 userId, date, theme)
|
|
- **业务数据通过模块自取** (如 stats, calendar items)
|
|
|
|
### 8.3 避免过度抽象
|
|
- 不要为了复用而复用
|
|
- 优先考虑**代码的清晰度**而非极致的 DRY
|
|
- Composables 应该解决**真实的重复问题**,而非预测性的抽象
|
|
|
|
---
|
|
|
|
## 9. 参考资源
|
|
|
|
**官方文档:**
|
|
- Vue 3 Composables: https://vuejs.org/guide/reusability/composables
|
|
- Vue 3 Performance: https://github.com/vuejs/docs/blob/main/src/guide/best-practices/performance.md
|
|
- Vue 3 State Management: https://vuejs.org/guide/scaling-up/state-management
|
|
|
|
**真实项目参考:**
|
|
- Soybean Admin: https://github.com/soybeanjs/soybean-admin (MIT)
|
|
- Art Design Pro: https://github.com/Daymychen/art-design-pro (MIT)
|
|
- Halo CMS: https://github.com/halo-dev/halo (GPL-3.0)
|
|
- DataEase: https://github.com/dataease/dataease (GPL-3.0)
|
|
|