为什么选择 InferDI
手动构造函数注入本来就是可靠的起点。依赖清晰可见,TypeScript 也能逐个检查 new 调用。当应用图超出几次构造,并且还要管理生命周期、作用域、异步就绪状态、模块和资源释放时,InferDI 才开始体现价值。
把组合当作一张图检查
每次注册都会返回一个新的容器类型。它记录已知键、服务类型、生命周期、异步状态和缺失的作用域输入。后续注册无法悄悄请求未知键、传入不兼容的构造函数参数、重复使用键,或让单例捕获生命周期更短的状态。
import { Container } from '@inferdi/inferdi'
class Database {
find(id: string) {
return { id }
}
}
class UserService {
constructor(readonly database: Database) {}
}
const app = new Container()
.registerClass('database', Database, [])
.registerClass('users', UserService, ['database'])
const users = app.get('users')InferDI 所说的“图就是类型”指的正是这份累积状态。组合拆到多个文件后,模块仍会保留相同的要求。
它比手动组装多做什么
手动调用已经能检查 new UserService(database)。不过,它不会自行记录注册的生命周期、异步状态如何沿依赖传播、可复用模块需要什么,也不会管理作用域拥有的缓存资源。InferDI 在保留显式选择的同时,补上整张图的契约和生命周期处理。
你仍然要写组装代码。代码审查时,团队可以直接看到选用了哪个实现,以及依赖以什么顺序传入。
小型运行时也会做实际工作
核心包没有运行时依赖,不要求装饰器或 metadata reflection,也不使用代理解析。生产包有低于 3 KiB gzip 的强制预算。缓存命中从一次 Map.get() 开始;显式 undefined 使用内部标记,无需再做一次查询。
类型会在编译后消失,但容器仍要注册 provider、创建对象、维护缓存、报告运行时错误并释放自己拥有的资源。默认模式保留循环和生命周期诊断。{ fast: true } 是单独启用的固定图契约,会减少部分运行时检查。
业务逻辑保持普通
领域服务接收普通的构造函数或函数参数,无需导入 InferDI,也无需持有 resolver。容器放在组合根和生命周期边界,在那里选择实现并创建作用域。
单元测试因此很直接:用假依赖实例化服务即可。更换 InferDI 仍需改写注册和框架胶水,但只要边界保持清楚,业务规则无需跟着改。组合根给出了完整示例。
明确的作用域和所有权
作用域可以对应 HTTP 请求、作业、租户操作或其他有边界的工作单元。容器会释放自己拥有并缓存的类和工厂结果。值、覆盖项、作用域输入和瞬态结果仍归调用方所有。父容器和子容器不会自动互相释放。
框架适配器把这些规则接到 React、Fastify、Hono、Koa、Express 和 Elysia 的生命周期上,核心包本身不加入框架语义。
保证的边界
TypeScript 仍是结构类型系统。结构相同的值会互相兼容,除非使用品牌类型;any 和类型断言也能绕过检查。宽泛的运行时键会降低图的精度。await 之后的动态依赖读取无法参与同步调用栈的循环追踪。
InferDI 不会替你设计架构、修复依赖循环、杜绝全部资源泄漏,也不会让所有应用都更快。它检查你声明的关系,并把运行时机制控制得很小。接下来可以阅读快速开始,或查看类型安全中的真实诊断。
