Nuxt应用公共层与共享能力治理重构
这次整理的对象主要来自下面这些本地素材:
11Vue学习/组件化思维/Nuxt业务模块与feature目录边界重构.md11Vue学习/组件化思维/Nuxt自动导入与隐式依赖边界重构.md11Vue学习/组件化思维/Nuxt组合式能力分层与页面编排边界重构.md11Vue学习/组件化思维/Nuxt中间件与插件注入边界重构.md11Vue学习/组件化思维/NuxtServerRoutes与BFF接口边界重构.md11Vue学习/组件化思维/Vuex状态中心与Pinia迁移重构.md11Vue学习/Nuxt4/核心概念/1Auto-imports.md
和前面的案例一样,这一轮不直接修改任何独立项目,而是把其中最值得复用的“Nuxt 应用公共层与共享能力治理”思路整理成一篇案例文档。
这类场景最容易被低估的地方,不是有没有公共层,而是“哪些能力真的值得进入应用公共层,哪些只是某个业务模块暂时看起来可复用”。如果这个问题没有先想清楚,项目后面通常会逐渐出现这些症状:
composables/、utils/、stores/和components/里的公共能力越来越多,但真正稳定复用的比例越来越低- 某个业务模块刚抽出来的能力,很快就被提升到全局,结果命名越来越抽象
- feature 私有逻辑通过自动导入、公共组件和全局 store 重新泄漏到应用层
- 共享能力越来越难下线,公共层逐渐变成“历史兼容仓库”
- 新需求一来,团队第一反应总是往全局目录里加,而不是先留在 feature 内部验证
这类场景真正的复杂度中心
Nuxt 公共层治理场景里,真正的复杂度中心不是“有没有抽公共”,而是“公共层到底应该承接哪些稳定协议”。
从当前素材和已有案例里,可以明确看到至少四条关键边界:
- 应用公共层和 feature 私有实现不是一回事
- 稳定共享协议和暂时重复代码不是一回事
- 跨业务基础设施和业务流程桥接不是一回事
- 提升到公共层和继续留在模块内部演化不是一回事
如果这些边界不先拆开,项目很快就会出现一些典型问题:
- 一个通用
useListPage()既接 query、又接表格、又接埋点,最后谁都能用一点,谁也不完全适用 - 公共
BaseDialog、BaseForm、BaseTable越来越重,内部充满单一业务分支判断 - 全局 store 承载越来越多本该只属于某个 feature 的局部状态
- 公共工具命名越来越抽象,但一删就会牵动很多业务历史包袱
所以这类重构的重点不是继续新建 shared/ 或 common/ 目录,而是先回答:
- 哪些能力已经形成稳定共享协议
- 哪些能力仍然应该留在业务模块内继续演化
- 应用公共层应该如何限制暴露面和命名抽象度
- 伪复用能力应该如何识别、回收和降级回 feature 内部
推荐的重构边界
更适合长期维护的 Nuxt 项目,通常会把公共层拆成下面几类:
- 应用基础设施层:例如请求客户端、会话读取、权限校验、日志和主题能力
- 稳定共享协议层:例如分页 query、表格列协议、表单提交流程、空态与错误态约定
- 设计系统层:真正跨业务稳定复用的 UI 组件和样式变量
- feature 私有实现层:默认承接业务步骤、页面编排和局部桥接,不急着提升到公共层
- 回收治理层:定期识别伪复用能力,把只服务单一模块的“公共能力”退回模块内部
如果换成更具体的理解,大概可以这样映射:
composables/useUserSession.ts:适合保留在应用公共层composables/usePaginationQuery.ts:如果被多个业务稳定复用,适合进入共享协议层components/AppEmptyState.vue:如果空态协议稳定,适合进入设计系统层features/orders/composables/useOrderExport.ts:默认仍应留在订单模块内部stores/order-batch.ts:如果只被订单模块使用,不应进入全局公共 store
这里最重要的原则是:
- 公共层只承接稳定协议
- 共享能力必须先经过真实复用验证
- feature 默认拥有演化优先权
- 伪复用能力要允许回收,而不是只进不出
不要把“看起来重复”直接等同于“应该抽公共”
很多 Nuxt 项目最容易出现的误判,是只要看到两个模块里有相似代码,就立刻抽成公共 composable 或公共组件。短期看像是在减少重复,长期却可能把两个还没长稳的流程绑在一起。
下面这种抽象很常见:
export function useListPage() {
const route = useRoute()
const query = ref(route.query)
const loading = ref(false)
const error = ref(null)
function syncQuery() {}
function fetchList() {}
function trackExpose() {}
return {
query,
loading,
error,
syncQuery,
fetchList,
trackExpose,
}
}问题在于:
- 列表页看起来都像列表页,但真实业务协议可能完全不同
- query、取数、埋点和错误恢复并没有形成稳定共享契约
- 一旦某个业务模块需要特殊流程,这个公共能力就会快速长出分支判断
更稳的做法,是先把重复部分拆成更小、更稳定的共享协议:
usePaginationQuery()只处理分页 queryuseRequestState()只处理提交或加载状态- feature 页面 composable 继续在模块内编排这些稳定协议
这样抽出来的公共层会更小,但也更稳。
应用公共层适合提供基础设施,不适合吞掉业务桥接层
Nuxt 公共层里最容易越界的一类能力,是“看起来谁都能用”的业务桥接层。比如:
- 同时理解页面 query、接口契约和表格渲染的列表页桥接
- 同时理解用户身份、按钮显隐和弹窗流程的权限桥接
- 同时理解表单字段、页面跳转和接口提交的业务提交流程
这类能力短期能帮某个页面减重,但如果直接进入全局公共层,通常会让:
- 业务差异被强行压平
- 公共 API 变得越来越抽象
- 调用方看起来更简单,底层实现却越来越难维护
更稳的做法是:
- 应用公共层提供基础设施和稳定协议
- feature 内部桥接层继续理解本业务语义
- 页面只消费页面业务 composable,而不是越过模块直接消费公共桥接
伪复用能力要允许回收,不要把公共层当终点站
很多团队有一个默认前提:只要能力进了公共层,就说明它已经“升级成功”。但从长期维护角度看,更健康的模型其实应该允许公共层回收。
一个很实用的判断标准是:
- 过去几个迭代里,它是否仍然被多个业务稳定复用
- 它的 API 是否越来越依赖某个单一业务语义
- 它是否开始为了兼容历史调用不断长参数和分支
如果答案越来越偏向单一业务,就应该考虑把它降级回 feature 内部,而不是硬保在公共层。
这件事很重要。因为只有允许“回收”,公共层才不会一路膨胀成历史包袱仓库。
页面、feature 和公共层之间要形成稳定升级路径
更适合长期维护的 Nuxt 项目,通常会让能力沿着下面这条路径演进:
- 第一步:先留在 feature 内部解决当前业务问题
- 第二步:等多个业务出现稳定重复后,再抽出共享协议
- 第三步:只有当协议真正稳定后,才提升到应用公共层
- 第四步:如果后续复用消失或业务语义回流,再允许降级回 feature
feature private
-> repeated in multiple features
-> extract stable shared protocol
-> promote to app shared layer
-> downgrade if pseudo-reuse appears这个路径最有价值的地方,不是“流程完整”,而是它让团队默认接受:公共层不是奖励,而是责任更重的一层。
这篇案例最后沉淀出的核心方法
这轮最重要的不是再加更多共享目录,而是沉淀出一条可复用的 Nuxt 公共层治理思路:
- 先区分基础设施、稳定共享协议和 feature 私有桥接三类能力
- 再让共享能力必须经过真实复用验证,而不是看到重复就立即提升
- 再建立从 feature 到公共层、再从公共层回收的双向治理路径
- 最后让应用公共层保持小而稳,把业务演化空间优先留给 feature
这样以后面对后台中台系统、内容平台、多业务管理端或长期演进的 Nuxt 应用时,就能先治理共享边界,而不是继续让公共层无上限膨胀。
