Skip to content

API 文档

Container

ts
const container = new Container();

具体文档参考这里。

Binding

container.bind(token) 返回一个 Binding 对象,通过 Binding 可以配置 token 对应的服务类型(toSelf()to()toConstantValue()toDynamicValue()toService())以及生命周期回调(onActivation()onDeactivation())。

详细说明参考 Binding 文档

Token

ts
const token = new Token<ServiceType>(name: string)

示例:

ts
// 定义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

ts
new LazyToken(() => Token | Newable);

示例:

ts
// 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

ts
@Injectable()

@Injectable 是一个无参数的类装饰器,用于在类定义阶段将装饰器元数据关联到类。

使用了 @Inject@PostConstruct@PreDestroy 的类必须添加 @Injectable

仅使用 @LazyInject 的类不需要 @Injectable

使用 decorate() 的类不需要 @Injectabledecorate() 内部已模拟 @Injectable 行为)。

@Injectable 放在类声明的最外层(最上面的装饰器位置),使用 @Injectable() 调用形式,与其他装饰器保持一致。

用法:

ts
@Injectable()
class DemoService {
  @Inject(LoggerService)
  public loggerService!: LoggerService;

  @PostConstruct()
  init() {
    console.log('initialized');
  }
}

@Inject

ts
@Inject(Token | Newable | LazyToken)

用法:

ts
// 定义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

用法:

ts
// 定义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

ts
@PostConstruct(void | boolean | Array<Token|Newable> | FilterFunction)

示例1:代替原来的construct方法

ts
@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:等待依赖注入完成后再初始化自身

ts
@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

示例:

ts
@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

ts
@autobind

@autobind 是一个无参数的方法装饰器,用于自动绑定方法的 this 到实例。

解决 服务的方法 作为回调传递时丢失 this 的问题,例如 Vue 模板中 @click="service.method"promise.then(service.method) 等场景。

通过 context.addInitializer 在实例创建时执行 bind(this),每个实例都会拥有自己的绑定版本,互不影响。

用法:

ts
import { autobind, Injectable } from '@kaokei/di';

@Injectable()
class UserService {
  public name = 'Alice';

  @autobind
  public greet() {
    return `Hello, ${this.name}`;
  }
}

绑定后的方法即使解构使用也不会丢失 this

ts
const service = container.get(UserService);
const { greet } = service;
greet(); // "Hello, Alice"  ✅ 不会报错

更多说明参考这里。

decorate

具体文档参考这里。

错误类

本库导出了 7 个错误类(BaseErrorBindingNotFoundErrorBindingNotValidErrorCircularDependencyErrorContainerNotFoundErrorDuplicateBindingErrorPostConstructError),详细说明参考 错误类文档

类型导出

本库导出了多个 TypeScript 类型(NewableCommonTokenActivationHandler 等),详细说明参考 类型导出文档