前端工程化 - 良好的feature-based-目录结构与具体示例
良好的feature-based-目录结构与具体示例
背景
先拆”业务边界”,不是拆组件
从业务角度来说,这个订单页其实有3个部分:
- 核心 - 浏览能力
- 订单列表
- 基础筛选
- 分页
- Extension - 可选 - 插件能力
- 高级筛选
- 导出
- 状态变更
- Detail - 按需能力
- 订单详情单床
重构目录结构
把 “ 按技术类型” 改为 “按业务角色”
如果一开始就能拆成这样,其实80%的问题就已经解决了.
src/
├── features/
│ ├── user/
│ │ ├── pages/
│ │ │ ├── UserList.vue
│ │ │ └── UserDetail.vue
│ │ │
│ │ ├── components/
│ │ │ ├── UserTable.vue
│ │ │ └── UserForm.vue
│ │ │
│ │ ├── api/
│ │ │ └── user.api.ts
│ │ │
│ │ ├── store/
│ │ │ └── user.store.ts
│ │ │
│ │ ├── hooks/
│ │ │ └── useUser.ts
│ │ │
│ │ ├── types/
│ │ │ └── user.types.ts
│ │ │
│ │ └── index.ts
│ │
│ ├── order/
│ │ ├── pages/
│ │ ├── components/
│ │ ├── api/
│ │ ├── store/
│ │ └── index.ts
│ │
│ └── product/
│ └── ...
│
├── shared/
│ ├── components/
│ │ ├── BaseTable.vue
│ │ ├── BaseModal.vue
│ │ └── BaseButton.vue
│ │
│ ├── hooks/
│ │ └── useRequest.ts
│ │
│ ├── utils/
│ └── styles/
│
├── router/
│ ├── routes/
│ │ ├── user.routes.ts
│ │ ├── order.routes.ts
│ │ └── product.routes.ts
│ │
│ └── index.ts
│
├── app.vue
└── main.ts
让 Page 变成 “纯装配层”
// OrderPage.vue(正确版本)
<template>
<div class="order-page">
<!-- Core:必须同步 -->
<BaseFilter />
<OrderList @select="openDetail" />
<!-- Extension:按需 -->
<Suspense>
<AdvancedFilter v-if="showAdvanced" />
</Suspense>
<Suspense>
<ExportPanel v-if="canExport" />
</Suspense>
<Suspense>
<StatusAction v-if="canChangeStatus" />
</Suspense>
<!-- Detail:用户触发 -->
<Suspense>
<OrderDetailDialog
v-if="showDetail"
:order-id="currentOrderId"
@close="closeDetail"
/>
</Suspense>
</div>
</template>
<script setup lang="ts">
import { defineAsyncComponent } from 'vue'
// core 同步加载
import BaseFilter from '../core/BaseFilter.vue'
import OrderList from '../core/OrderList.vue'
// extension 异步加载
const AdvancedFilter = defineAsyncComponent(
() => import('../extensions/advanced-filter/AdvancedFilter.vue')
)
const ExportPanel = defineAsyncComponent(
() => import('../extensions/export/ExportPanel.vue')
)
const StatusAction = defineAsyncComponent(
() => import('../extensions/status/StatusAction.vue')
)
// detail 异步
const OrderDetailDialog = defineAsyncComponent(
() => import('../detail/OrderDetailDialog.vue')
)
import { useOrderCore } from '../core/useOrderCore'
import { useOrderDetail } from '../detail/useOrderDetail'
const {
showAdvanced,
canExport,
canChangeStatus
} = useOrderCore()
const {
showDetail,
currentOrderId,
openDetail,
closeDetail
} = useOrderDetail()
</script>
- Page 里没有业务规则
- 没有 if 权限判断
- 只负责 “拼装能力”
把 规则 收敛到 对应边界
// useOrderCore.ts(核心逻辑)
export function useOrderCore() {
const showAdvanced = ref(false)
const canExport = computed(() => {
return permission.value.includes('order:export')
})
const canChangeStatus = computed(() => {
return permission.value.includes('order:status')
})
return {
showAdvanced,
canExport,
canChangeStatus
}
}
规则集中,边界生效
Extension 是“插件”,不污染 Core
不需要知道
OrderList的存在
// extensions/export/useExport.ts
export function useExport() {
const exportOrders = async () => {
// 导出逻辑
}
return { exportOrders }
}
// ExportPanel.vue
<template>
<button @click="exportOrders">导出</button>
</template>
<script setup>
import { useExport } from './useExport'
const { exportOrders } = useExport()
</script>
Detail 完全独立
Detail 不依赖 Core,Core 也不依赖 Detail。
// detail/useOrderDetail.ts
export function useOrderDetail() {
const showDetail = ref(false)
const currentOrderId = ref<string | null>(null)
const openDetail = (id: string) => {
currentOrderId.value = id
showDetail.value = true
}
const closeDetail = () => {
showDetail.value = false
}
return {
showDetail,
currentOrderId,
openDetail,
closeDetail
}
}
正确的拆分,是先让业务“各司其职”,
lazy 只是顺手的事。

浙公网安备 33010602011771号