9.1 KiB
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 视为组件级别的服务,它们可以相互通信
官方示例模式:
<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
- 仅在组件渲染时才加载,改善初始加载时间
import { defineAsyncComponent } from 'vue'
// Foo.vue 及其依赖被单独打包成一个 chunk
// 只有在组件被渲染时才会按需获取
const Foo = defineAsyncComponent(() => import('./Foo.vue'))
2.2 动态导入用于 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
<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
<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 类型声明模式:
const emit = defineEmits<{
'update:modelValue': [value: string];
'change': [event: Event];
'custom-event': [payload: CustomPayload];
}>();
Runtime 声明模式:
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
<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 自取数据的场景
- 模块是独立的业务单元(如日历、统计卡片)
- 数据获取逻辑属于模块内部关注点
- 模块需要定期刷新或重新加载数据
- 多个平行模块各自管理自己的状态
示例:
<!-- 统计卡片模块 - 自己获取数据 -->
<script setup lang="ts">
const { data, loading } = useBudgetStats()
onMounted(() => {
loadStats()
})
</script>
5.2 接收 Props 的场景
- 模块是展示组件(Presentational Component)
- 父组件需要协调多个子组件的数据
- 数据来源于全局状态管理(如 Pinia store)
- 需要在父组件层面做数据聚合或转换
示例:
<!-- 数据展示组件 - 接收 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):
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)