API 文档
Container
const container = new Container();Binding
container.bind(token) 返回一个 Binding 对象,通过 Binding 可以配置 token 对应的服务类型(toSelf()、to()、toConstantValue()、toDynamicValue()、toService())以及生命周期回调(onActivation()、onDeactivation())。
详细说明参考 Binding 文档。
Token
const token = new Token<ServiceType>(name: string)示例:
// 定义LoggerService类
class LoggerService {
log(msg: string) {
console.log(msg);
}
}
// 创建loggerToken,并且指定类型为LoggerService
// 注意这里的logger-service-name可以是任意的字符串,只用来标志loggerToken的名称
const loggerToken = new Token<LoggerService>('logger-service-name');
// 创建容器
const container = new Container();
// 绑定loggerToken和LoggerService类
container.bind(loggerToken).to(LoggerService);
// Class类也可以当作token来使用,此时可以使用toSelf快捷方法
// 效果类似container.bind(LoggerService).to(LoggerService);
container.bind(LoggerService).toSelf();
// 通过loggerToken获取logger实例,这里ide可以直接推导出logger1的类型是LoggerService
const logger1 = container.get(loggerToken);
// 通过LoggerService作为token来获取logger实例,logger2的类型也是LoggerService
const logger2 = container.get(LoggerService);需要注意container.bind方法只支持 Class 或者 Token 实例作为参数。而不能直接使用字符串和 Symbol 等其他类型作为参数。
需要注意文档中提到的 token 如果是狭义的概念,则代表是一个 Token 的实例对象。如果 token 是一个广义的概念,则可以是一个 Token 的实例对象,一个 Class 类,甚至是一个 LazyToken 的实例对象。需要注意区分。
这个例子中虽然 loggerToken 和 LoggerService 都作为 token 绑定了 LoggerService,但是 logger1 和 logger2 并不相等。
注意这里 container.bind 和 container.get 的过程非常像 map 中的 map.set 和 map.get 过程,只不过这里 container.get 时会有实例化的过程。
LazyToken
new LazyToken(() => Token | Newable);示例:
// a.ts 文件
import { B } from './b.ts';
@Injectable()
export class A {
public name = 'A';
@Inject(new LazyToken(() => B))
public b!: B;
}
// b.ts 文件
import { A } from './a.ts';
@Injectable()
export class B {
public name = 'B';
@Inject(new LazyToken(() => A))
public a!: A;
}使用场景就是在@Inject装饰器中懒加载依赖的 token。主要原因在于装饰器是立即执行的。所以会导致类 A 和类 B 互相循环依赖,最终导致编译失败。
但是 LazyToken 本身只能解决@Inject导致的循环依赖,并不能解决所有循环依赖。比如类 A 和类 B 如果在实例化过程中互相依赖,则仍然会抛出异常。
具体哪些场景的循环依赖是被支持的可以参考这里。
本库使用 TC39 Stage 3 装饰器规范,所有依赖声明统一通过实例属性装饰器(Field Decorator)完成,不支持构造函数参数装饰器(Parameter Decorator)。
@Injectable
@Injectable()@Injectable 是一个无参数的类装饰器,用于在类定义阶段将装饰器元数据关联到类。
使用了 @Inject、@PostConstruct、@PreDestroy 的类必须添加 @Injectable。
仅使用 @LazyInject 的类不需要 @Injectable。
使用 decorate() 的类不需要 @Injectable(decorate() 内部已模拟 @Injectable 行为)。
@Injectable 放在类声明的最外层(最上面的装饰器位置),使用 @Injectable() 调用形式,与其他装饰器保持一致。
用法:
@Injectable()
class DemoService {
@Inject(LoggerService)
public loggerService!: LoggerService;
@PostConstruct()
init() {
console.log('initialized');
}
}@Inject
@Inject(Token | Newable | LazyToken)用法:
// 定义LoggerService类
class LoggerService {
log(msg: string) {
console.log(msg);
}
}
// 定义CountService类
class CountService {
count = 0;
increase() {
this.count++;
}
}
@Injectable()
class DemoService {
// 使用场景: 指定注入属性的token
@Inject(LoggerService)
public loggerService!: LoggerService;
@Inject(CountService)
public countService!: CountService;
}@Inject 用于注入属性依赖(Field Decorator)。
@Inject必须指定一个 Token 实例对象或者 Class 类或者一个 LazyToken 实例对象,也就是@Inject的参数是必填项。
@Self + @SkipSelf + @Optional
用法:
// 定义LoggerService类
class LoggerService {
log(msg: string) {
console.log(msg);
}
}
@Injectable()
class DemoService {
@Self()
@Inject(LoggerService)
public loggerService1!: LoggerService;
@Optional()
@Self()
@Inject(LoggerService)
public loggerService2!: LoggerService;
@SkipSelf()
@Inject(LoggerService)
public loggerService3!: LoggerService;
@Optional()
@SkipSelf()
@Inject(LoggerService)
public loggerService4!: LoggerService;
}@Self 和 @SkipSelf是用于控制从哪一个容器开始查找对应的 token。
@Self指定只在当前 container 中查找对应的 token。
@SkipSelf指定跳过当前 container,从父级 container 中开始查找对应的 token。
@Optional指定当没有查找到对应的 token 时的行为,默认行为是抛出BindingNotFoundError,如果指定了@Optional则不抛出异常,相当于返回了 undefined。当然,如果指定了@Optional,那么在实际业务中需要注意判空。
Note
这 3 个装饰器来源于 Angular 的 API,但是实际上使用到的业务场景较少。
@PostConstruct
@PostConstruct(void | boolean | Array<Token|Newable> | FilterFunction)示例1:代替原来的construct方法
@Injectable()
class StudentService {
public student: StudentVO;
@PostConstruct()
init() {
return fetch('/api/get-student-info').then(res => (this.student = res));
}
}如果是手动new StudentService(),此时@PostConstruct是不起作用的。
只有通过 container 获取实例对象时,此时会在实例对象创建完成之后,自动调用init方法,从而自动完成 student 信息的获取。
这里的@PostConstruct()相当于是代替原来的construct()方法了,之所以这样是因为原来的construct方法执行的时候,依赖注入还没有完成,也就是依赖注入的属性还是空的,那么有些业务逻辑就没有办法在原来的construct()方法中实现了,所以必须通过@PostConstruct()来定义一个新的方法来代替原来的construct()方法。
示例1:等待依赖注入完成后再初始化自身
@Injectable()
class StudentService {
public student: StudentVO;
@PostConstruct()
init() {
return fetch('/api/get-student-info').then(res => (this.student = res));
}
}
@Injectable()
class ScoreService {
public score: ScoreVO;
@Inject(StudentService)
public studentService: StudentService;
@PostConstruct(true)
init() {
const studentId = this.studentService.student.id;
return fetch(`/api/get-score-info?id=${id}`).then(
res => (this.score = res)
);
}
}现在这里的 ScoreService 是依赖 StudentService 的,并且在初始化时就需要等待 StudentService 完全初始化完成。
所以需要通过@PostConstruct(true)来保证StudentService的init方法已经完成了数据的获取。这样ScoreService的init方法就可以访问到学生id了。
更多高级功能参考这里,比如等待异步服务初始化完成之后,再执行自己的初始化服务。
Note 继承行为:沿继承链向上查找,执行第一个找到的
@PostConstruct方法。详细说明参考 生命周期文档。
@PreDestroy
示例:
@Injectable()
class DatabaseService {
public db: DatabaseVO;
@PostConstruct()
init() {
this.db = createDatabaseClient();
}
@PreDestroy()
close() {
this.db.disconnect();
}
}@PreDestroy 是和 @PostConstruct对应的装饰器,被装饰的方法,这里是 close 方法会在container.unbind(DatabaseService对应的token)时自动调用,在这个例子中可以自动完成数据库连接的断开。
@LazyInject
同时还导出了 createLazyInject 高阶函数,可以返回一个绑定指定 container 的 LazyInject 装饰器。
@autobind
@autobind@autobind 是一个无参数的方法装饰器,用于自动绑定方法的 this 到实例。
解决 服务的方法 作为回调传递时丢失 this 的问题,例如 Vue 模板中 @click="service.method" 或 promise.then(service.method) 等场景。
通过 context.addInitializer 在实例创建时执行 bind(this),每个实例都会拥有自己的绑定版本,互不影响。
用法:
import { autobind, Injectable } from '@kaokei/di';
@Injectable()
class UserService {
public name = 'Alice';
@autobind
public greet() {
return `Hello, ${this.name}`;
}
}绑定后的方法即使解构使用也不会丢失 this:
const service = container.get(UserService);
const { greet } = service;
greet(); // "Hello, Alice" ✅ 不会报错decorate
错误类
本库导出了 7 个错误类(BaseError、BindingNotFoundError、BindingNotValidError、CircularDependencyError、ContainerNotFoundError、DuplicateBindingError、PostConstructError),详细说明参考 错误类文档。
类型导出
本库导出了多个 TypeScript 类型(Newable、CommonToken、ActivationHandler 等),详细说明参考 类型导出文档。