前端工程化 - 良好的feature-based-目录结构与具体示例
<h1 id="良好的feature-based-目录结构与具体示例">良好的feature-based-目录结构与具体示例</h1><h3 id="背景">背景</h3>
<blockquote>
<p>先拆”业务边界”,不是拆组件</p>
</blockquote>
<p>从业务角度来说,这个订单页其实有3个部分:</p>
<ol>
<li>核心 - 浏览能力
<ul>
<li>订单列表</li>
<li>基础筛选</li>
<li>分页</li>
</ul>
</li>
<li>Extension- 可选 - 插件能力
<ul>
<li>高级筛选</li>
<li>导出</li>
<li>状态变更</li>
</ul>
</li>
<li>Detail - 按需能力
<ul>
<li>订单详情单床</li>
</ul>
</li>
</ol>
<h3 id="重构目录结构">重构目录结构</h3>
<blockquote>
<p>把 “ 按技术类型” 改为 “按业务角色”</p>
</blockquote>
<p>如果一开始就能拆成这样,其实80%的问题就已经解决了.</p>
<pre><code class="language-html">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
</code></pre>
<h3 id="让-page-变成-纯装配层">让 Page 变成 “纯装配层”</h3>
<pre><code class="language-html">// 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>
</code></pre>
<ul>
<li>Page 里没有业务规则</li>
<li>没有 if 权限判断</li>
<li>只负责 “拼装能力”</li>
</ul>
<h3 id="把-规则-收敛到-对应边界">把 规则 收敛到 对应边界</h3>
<pre><code class="language-tsx">// 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
}
}
</code></pre>
<p>规则集中,边界生效</p>
<h3 id="extension-是插件不污染-core">Extension 是“插件”,不污染 Core</h3>
<blockquote>
<p>不需要知道 <code>OrderList</code>的存在</p>
</blockquote>
<pre><code class="language-tsx">// 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>
</code></pre>
<h3 id="detail-完全独立">Detail 完全独立</h3>
<blockquote>
<p><strong>Detail 不依赖 Core,Core 也不依赖 Detail</strong>。</p>
</blockquote>
<pre><code class="language-tsx">// 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
}
}
</code></pre>
<blockquote>
<p>正确的拆分,是先让业务“各司其职”,<br>
lazy 只是顺手的事。</p>
</blockquote><br><br>
来源:https://www.cnblogs.com/mt-joer/p/19604149
頁:
[1]