Skip to content

08|目录与依赖边界 ​

第九章 / 08。目标:目录表达所有权,规则能在 CI 中判定,而不是靠“大家自觉”。

从允许方向开始 ​

text
app        → pages, features, shared
pages      → features, entities, shared
features   → entities, shared
entities   → shared
shared     → 外部包;不能反向导入业务层

这是一个示例有向图。团队也可按领域垂直切分,但必须回答:谁拥有状态/请求、公共入口在哪、跨域依赖经什么接口、禁止方向是什么。

text
src/features/cart/
  api/          请求适配
  model/        状态和业务规则
  ui/           组件
  index.ts      对外公共入口

外部模块只从 features/cart 公共入口导入,不穿透 model/internal.ts。barrel 不是越多越好:无边界聚合会制造循环依赖、扩大初始化副作用和打包面。

状态与副作用放在哪里 ​

  • 只为一个组件服务的开关/输入留在组件。
  • 可复用交互流程放 feature composable,并注入请求依赖。
  • 后端实体形状在 API adapter 解析成领域模型,组件不直接依赖原始 JSON。
  • 跨页面长期状态才进入 store;store 不是所有变量的默认家。

类型 import 也表达架构依赖。它可能被擦除而不进入运行时代码,但仍会让低层知道高层业务概念;边界规则应同时检查 value/type import。

把规则变成失败 ​

ESLint no-restricted-imports、依赖图工具或自写扫描器都可以。关键输出要包含 importer、imported 和违反的允许边,且循环检测列出完整路径。只画架构图而 CI 不执行,目录会逐渐失真。

迁移时先添加观测模式生成现有违例清单,再按领域修复并逐步收紧;一次禁止全部历史例外会迫使团队关闭规则。

检测环要区分“以前走过”与“正在这条递归路径上”。从A走到B再到C,若C指向A,而A仍在当前路径,就找到了 A→B→C→A;若指向已经完整检查过且退出路径的D,则只是复用一个依赖,不是环。DFS(深度优先遍历)每次先走一条边到尽头,再返回,分别用 visiting/visited 两个集合记录这两种状态,答案展示完整实现。

allowed 的键定义全部已知层,包括没有出边的 shared,写成 shared: []。未知目标即便误填进某个允许列表也应报错,不能把规则拼错当成新增了一层。

练习:验证依赖图 ​

实现 validateEdges(edges, allowed)。edge 为 [fromLayer,toLayer];返回全部非法边,不提前停止;层名未知也算非法。再实现 DFS 检测至少一条循环路径。用 shared→feature、feature→entity、entity→shared、feature→feature(同层允许与否由 allowed 明确)和三节点环写断言。

参考答案:第 9 章第 08 题。

用心记录每一步成长