Skip to content

Nuxt应用公共层与共享能力治理重构

这次整理的对象主要来自下面这些本地素材:

  • 11Vue学习/组件化思维/Nuxt业务模块与feature目录边界重构.md
  • 11Vue学习/组件化思维/Nuxt自动导入与隐式依赖边界重构.md
  • 11Vue学习/组件化思维/Nuxt组合式能力分层与页面编排边界重构.md
  • 11Vue学习/组件化思维/Nuxt中间件与插件注入边界重构.md
  • 11Vue学习/组件化思维/NuxtServerRoutes与BFF接口边界重构.md
  • 11Vue学习/组件化思维/Vuex状态中心与Pinia迁移重构.md
  • 11Vue学习/Nuxt4/核心概念/1Auto-imports.md

和前面的案例一样,这一轮不直接修改任何独立项目,而是把其中最值得复用的“Nuxt 应用公共层与共享能力治理”思路整理成一篇案例文档。

这类场景最容易被低估的地方,不是有没有公共层,而是“哪些能力真的值得进入应用公共层,哪些只是某个业务模块暂时看起来可复用”。如果这个问题没有先想清楚,项目后面通常会逐渐出现这些症状:

  • composables/utils/stores/components/ 里的公共能力越来越多,但真正稳定复用的比例越来越低
  • 某个业务模块刚抽出来的能力,很快就被提升到全局,结果命名越来越抽象
  • feature 私有逻辑通过自动导入、公共组件和全局 store 重新泄漏到应用层
  • 共享能力越来越难下线,公共层逐渐变成“历史兼容仓库”
  • 新需求一来,团队第一反应总是往全局目录里加,而不是先留在 feature 内部验证

这类场景真正的复杂度中心

Nuxt 公共层治理场景里,真正的复杂度中心不是“有没有抽公共”,而是“公共层到底应该承接哪些稳定协议”。

从当前素材和已有案例里,可以明确看到至少四条关键边界:

  • 应用公共层和 feature 私有实现不是一回事
  • 稳定共享协议和暂时重复代码不是一回事
  • 跨业务基础设施和业务流程桥接不是一回事
  • 提升到公共层和继续留在模块内部演化不是一回事

如果这些边界不先拆开,项目很快就会出现一些典型问题:

  • 一个通用 useListPage() 既接 query、又接表格、又接埋点,最后谁都能用一点,谁也不完全适用
  • 公共 BaseDialogBaseFormBaseTable 越来越重,内部充满单一业务分支判断
  • 全局 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 或公共组件。短期看像是在减少重复,长期却可能把两个还没长稳的流程绑在一起。

下面这种抽象很常见:

ts
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() 只处理分页 query
  • useRequestState() 只处理提交或加载状态
  • feature 页面 composable 继续在模块内编排这些稳定协议

这样抽出来的公共层会更小,但也更稳。

应用公共层适合提供基础设施,不适合吞掉业务桥接层

Nuxt 公共层里最容易越界的一类能力,是“看起来谁都能用”的业务桥接层。比如:

  • 同时理解页面 query、接口契约和表格渲染的列表页桥接
  • 同时理解用户身份、按钮显隐和弹窗流程的权限桥接
  • 同时理解表单字段、页面跳转和接口提交的业务提交流程

这类能力短期能帮某个页面减重,但如果直接进入全局公共层,通常会让:

  • 业务差异被强行压平
  • 公共 API 变得越来越抽象
  • 调用方看起来更简单,底层实现却越来越难维护

更稳的做法是:

  • 应用公共层提供基础设施和稳定协议
  • feature 内部桥接层继续理解本业务语义
  • 页面只消费页面业务 composable,而不是越过模块直接消费公共桥接

伪复用能力要允许回收,不要把公共层当终点站

很多团队有一个默认前提:只要能力进了公共层,就说明它已经“升级成功”。但从长期维护角度看,更健康的模型其实应该允许公共层回收。

一个很实用的判断标准是:

  • 过去几个迭代里,它是否仍然被多个业务稳定复用
  • 它的 API 是否越来越依赖某个单一业务语义
  • 它是否开始为了兼容历史调用不断长参数和分支

如果答案越来越偏向单一业务,就应该考虑把它降级回 feature 内部,而不是硬保在公共层。

这件事很重要。因为只有允许“回收”,公共层才不会一路膨胀成历史包袱仓库。

页面、feature 和公共层之间要形成稳定升级路径

更适合长期维护的 Nuxt 项目,通常会让能力沿着下面这条路径演进:

  • 第一步:先留在 feature 内部解决当前业务问题
  • 第二步:等多个业务出现稳定重复后,再抽出共享协议
  • 第三步:只有当协议真正稳定后,才提升到应用公共层
  • 第四步:如果后续复用消失或业务语义回流,再允许降级回 feature
text
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 应用时,就能先治理共享边界,而不是继续让公共层无上限膨胀。

共 20 个模块,1301 篇 Markdown 文档。