关键在于通过模块边界设计和导出策略切断访问路径:不导出模型变量或构造器,只暴露稳定契约接口;禁用default导出,强制语义化命名;用api.ts作导出防火墙,配合工具链自动拦截非法导出。

要让内部数据模型变量不被外部业务逻辑直接依赖,关键不是“用 exports 暴露什么”,而是通过模块边界设计和导出策略主动切断访问路径。exports 本身不具备访问控制能力,它只是 CommonJS 的导出机制;真正的隔离靠的是“不导出”+“只导出契约接口”。
只暴露稳定接口,不导出模型变量或构造器
数据模型(如 OrderState 类、cartData 对象)属于实现细节,不应出现在导出列表中。外部应通过服务方法间接操作,而非直接读写状态。
- ❌ 错误做法:导出原始模型
// model.js
class OrderState {
constructor() { this.items = []; }
}
export const cartData = { total: 0 };
module.exports = { OrderState, cartData };
- ✅ 正确做法:封装为不可变接口
// api.js(顶层统一出口)
import { OrderService } from './core/service';
export const getOrderSummary = () => OrderService.getSummary();
export const addItemToCart = (item) => OrderService.addItem(item);
export const clearCart = () => OrderService.clear();
禁用默认导出,强制语义化引用
使用 export const 或 export function 明确标识每个能力的用途,避免外部随意解构或重命名破坏契约一致性。
- 禁止
export default {...}—— 它掩盖了“这是接口”还是“这是实现”的本质 - 所有导出名必须带业务语义,如
validateShippingAddress,而非run或doLogic - 配合 TypeScript 的
@publicJSDoc 标记,在 IDE 中限制自动补全范围
用顶层 api.ts 做“导出防火墙”
底层目录(如 ./model/、./utils/)全部设为私有——不对外暴露任何路径。所有对外能力必须经由 api.ts 显式 re-export,并可叠加校验、日志、错误包装等中间层。
- 不允许外部写
import { CartModel } from './model/cart' - 只允许
import { addToCart } from './api' - 可在 api 层做输入校验:
export const addToCart = (input) => { assertValid(input); return _addToCart(input); }
构建时自动拦截非法导出
仅靠人工约定容易失效,需工具链兜底:
- 启用 ESLint 规则
import/no-unused-modules,自动报出未被api.ts引用的导出项 - CI 中运行 AST 扫描,检测是否出现跨模块直引模型文件(如
payment/model被reporting/导入) - 配置
tsconfig.json的"noUnusedLocals": true和"noUnusedParameters": true,减少隐性暴露可能

















