核心是让业务逻辑与框架彻底解耦,关键动作是将框架相关操作推至边界层、业务模块仅依赖接口契约;通过集中工厂函数创建服务、接口抽象导航与存储等能力、视图层仅作数据绑定与事件触发,实现跨框架可移植性。

核心不是引入 DI 框架,而是让业务逻辑彻底不知道自己运行在 Vue、React 还是纯 JS 环境里。关键动作是:把框架相关操作(如响应式更新、生命周期钩子、DOM 访问)全部推到边界层,业务模块只依赖接口契约,不碰任何框架 API。
先切断 new,把实例创建权交给统一入口
搜索代码中所有 new ApiClient()、new UserManager()、Config.getInstance() 这类调用——它们是耦合的起点。不要逐个替换为 inject(),而是立刻收口到一个集中工厂函数:
- 写一个 createBusinessServices() 函数,在应用启动时(如 main.ts 或 app.ts)一次性执行,返回包含所有核心服务的对象
- 业务类(如 OrderProcessor、ReportGenerator)只通过构造函数接收这些服务,自身绝不 new、不 import 框架模块
- 测试时直接传入 mock 实现,完全绕过框架初始化流程
用接口定义契约,而非暴露具体类或框架类型
避免写 import { Router } from 'vue-router' 进入业务文件。取而代之的是抽象出行为契约:
- 定义 NavigationService 接口,含 goTo(path: string)、back() 方法
- 定义 StorageService 接口,含 save(key: string, data: any)、load
(key: string): T | null - 框架适配层(如 routerAdapter.ts)实现这些接口,内部调用 Vue Router 或 React Router API
- 业务模块只依赖接口,编译时和运行时都与框架无关
视图层只做“翻译”,不承载业务判断
组件内部不应出现 if-else 分支处理不同业务状态(如 “如果用户是 VIP 就显示折扣”)。这类逻辑必须上提至 ViewModel 或独立的业务协调器:
- 组件只接收 userState: UserViewState 这样的扁平数据结构,由外部计算好并注入
- View 组件只做两件事:绑定数据、触发事件(如 click → emit('submit')),不调用 service、不读 localStorage、不跳转路由
- 路由跳转、弹窗控制、API 调用等副作用,均由注入的服务或 Coordinator 执行
提供稳定服务实例,慎用 provide/inject 的动态创建
Vue 的 provide/inject 不是 DI 容器,它只是传递机制。错误做法是在 setup 中每次 new 一个 ApiClient 再 provide;正确做法是:
- 在应用最外层(如 createApp 后、挂载前)调用 createServices(),生成单例服务对象
- 使用 provide('services', services) 提供整个对象,而不是 provide('api', new ApiClient())
- 消费方用 const { api } = inject('services') 解构,确保复用同一实例,且 IDE 可识别类型
- TypeScript 中必须用 InjectionKey 显式声明,否则类型丢失、重构失效










