依赖注入(DI)是一种实现控制反转的设计模式,在前端工程中可以有效降低模块间的耦合度,让代码更易测试和维护。轻量级DI容器不需要复杂的配置和庞大的依赖,适合中小型前端项目使用,下面我们就来实现一个基础版本的前端DI容器。

依赖注入容器核心功能
一个基础的轻量级DI容器需要支持以下几个核心能力:
- 依赖注册:将类、函数或者实例注册到容器中,指定对应的标识符
- 依赖解析:根据标识符从容器中获取对应的依赖实例,自动处理依赖的依赖关系
- 生命周期管理:支持单例、瞬态等不同生命周期的实例管理
- 类型安全:结合TypeScript的类型系统,避免依赖获取时的类型错误
基础实现步骤
1. 定义类型和生命周期枚举
首先我们需要定义容器用到的类型,以及实例的生命周期类型,方便后续扩展和管理。
// 定义生命周期枚举
enum Lifecycle {
// 单例:容器中只存在一个实例
SINGLETON = 'singleton',
// 瞬态:每次获取都创建新实例
TRANSIENT = 'transient'
}
// 定义依赖的注册项类型
interface DependencyRecord<T = any> {
// 依赖的构造函数或者工厂函数
factory: () => T;
// 生命周期类型
lifecycle: Lifecycle;
// 缓存的单例实例,仅单例模式下使用
instance?: T;
}
2. 实现DI容器核心类
接下来实现容器的核心类,包含注册和解析两个核心方法,同时处理依赖的递归解析逻辑。
class DIContainer {
// 存储所有注册的依赖,key为依赖标识符,value为依赖注册项
private dependencies: Map<string | symbol, DependencyRecord> = new Map();
/**
* 注册依赖到容器中
* @param identifier 依赖的唯一标识符
* @param factory 创建依赖实例的工厂函数
* @param lifecycle 依赖的生命周期,默认单例
*/
register<T>(
identifier: string | symbol,
factory: () => T,
lifecycle: Lifecycle = Lifecycle.SINGLETON
): void {
if (this.dependencies.has(identifier)) {
throw new Error(`依赖 ${String(identifier)} 已经被注册`);
}
this.dependencies.set(identifier, {
factory: factory as () => any,
lifecycle,
instance: undefined
});
}
/**
* 从容器中解析依赖
* @param identifier 依赖的唯一标识符
* @returns 对应的依赖实例
*/
resolve<T>(identifier: string | symbol): T {
const record = this.dependencies.get(identifier);
if (!record) {
throw new Error(`未找到标识符为 ${String(identifier)} 的依赖`);
}
// 单例模式:如果已经有缓存实例直接返回,否则创建后缓存
if (record.lifecycle === Lifecycle.SINGLETON) {
if (!record.instance) {
record.instance = record.factory();
}
return record.instance as T;
}
// 瞬态模式:每次都创建新实例
return record.factory() as T;
}
/**
* 清空容器中所有注册的依赖
*/
clear(): void {
this.dependencies.clear();
}
}
3. 使用示例
下面通过一个简单的业务场景演示这个DI容器的使用方式,包含服务的定义和依赖注入过程。
// 定义服务A
class ServiceA {
getData(): string {
return '这是来自ServiceA的数据';
}
}
// 定义服务B,依赖ServiceA
class ServiceB {
private serviceA: ServiceA;
constructor(serviceA: ServiceA) {
this.serviceA = serviceA;
}
getCombinedData(): string {
return `ServiceB获取数据:${this.serviceA.getData()}`;
}
}
// 创建DI容器实例
const container = new DIContainer();
// 注册依赖,这里手动处理构造函数的参数注入
container.register(ServiceA, () => new ServiceA(), Lifecycle.SINGLETON);
container.register(
ServiceB,
() => new ServiceB(container.resolve(ServiceA)),
Lifecycle.TRANSIENT
);
// 解析依赖并使用
const serviceB = container.resolve<ServiceB>(ServiceB);
console.log(serviceB.getCombinedData()); // 输出:ServiceB获取数据:这是来自ServiceA的数据
优化方向
上面的实现是一个最基础的版本,实际使用中可以根据需求做更多优化:
- 支持装饰器语法,自动收集类的依赖信息,减少手动注册的工作量
- 增加循环依赖检测,避免依赖解析时出现无限递归的问题
- 支持可选依赖,当依赖不存在时返回默认值而不是抛出错误
- 结合前端框架的插件机制,适配React、Vue等框架的生态系统
注意事项
在使用自实现的DI容器时需要注意几个问题:
不要在工厂函数中做过于复杂的逻辑,避免依赖解析时的性能问题。如果依赖需要异步初始化,需要额外扩展容器支持异步工厂函数。
另外,轻量级容器的优势在于简单可控,如果项目规模较大,依赖关系非常复杂,也可以考虑使用社区成熟的DI库,避免重复造轮子带来的维护成本。
依赖注入DI容器前端架构TypeScript修改时间:2026-06-18 18:54:41