铸著竹煮烛 發表於 2026-2-11 14:09:00

前端工程化 - 良好的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(正确版本)

&lt;template&gt;
&lt;div class="order-page"&gt;
    &lt;!-- Core:必须同步 --&gt;
    &lt;BaseFilter /&gt;
    &lt;OrderList @select="openDetail" /&gt;

    &lt;!-- Extension:按需 --&gt;
    &lt;Suspense&gt;
      &lt;AdvancedFilter v-if="showAdvanced" /&gt;
    &lt;/Suspense&gt;

    &lt;Suspense&gt;
      &lt;ExportPanel v-if="canExport" /&gt;
    &lt;/Suspense&gt;

    &lt;Suspense&gt;
      &lt;StatusAction v-if="canChangeStatus" /&gt;
    &lt;/Suspense&gt;

    &lt;!-- Detail:用户触发 --&gt;
    &lt;Suspense&gt;
      &lt;OrderDetailDialog
      v-if="showDetail"
      :order-id="currentOrderId"
      @close="closeDetail"
      /&gt;
    &lt;/Suspense&gt;
&lt;/div&gt;
&lt;/template&gt;
&lt;script setup lang="ts"&gt;
import { defineAsyncComponent } from 'vue'

// core 同步加载
import BaseFilter from '../core/BaseFilter.vue'
import OrderList from '../core/OrderList.vue'

// extension 异步加载
const AdvancedFilter = defineAsyncComponent(
() =&gt; import('../extensions/advanced-filter/AdvancedFilter.vue')
)
const ExportPanel = defineAsyncComponent(
() =&gt; import('../extensions/export/ExportPanel.vue')
)
const StatusAction = defineAsyncComponent(
() =&gt; import('../extensions/status/StatusAction.vue')
)

// detail 异步
const OrderDetailDialog = defineAsyncComponent(
() =&gt; 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()
&lt;/script&gt;
</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(() =&gt; {
    return permission.value.includes('order:export')
})

const canChangeStatus = computed(() =&gt; {
    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 () =&gt; {
    // 导出逻辑
}

return { exportOrders }
}

// ExportPanel.vue

&lt;template&gt;
&lt;button @click="exportOrders"&gt;导出&lt;/button&gt;
&lt;/template&gt;

&lt;script setup&gt;
import { useExport } from './useExport'
const { exportOrders } = useExport()
&lt;/script&gt;
</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&lt;string | null&gt;(null)

const openDetail = (id: string) =&gt; {
    currentOrderId.value = id
    showDetail.value = true
}

const closeDetail = () =&gt; {
    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]
查看完整版本: 前端工程化 - 良好的feature-based-目录结构与具体示例