文档依赖项

依赖项

redi 支持以下几种依赖项:

  • 类依赖项
  • 值依赖项
  • 工厂依赖项
  • 异步依赖项

类依赖项

类可以直接作为依赖项:

class AuthService {}

也是可以一个符合 ClassDependencyItem 接口的对象:

export interface ClassDependencyItem<T> {
  useClass: Ctor<T>;
  lazy?: boolean;
}

惰性实例化

你可以将 lazy 设置为 true,这样该依赖就不会在它的依赖实例化时被实例化,而是等到实际被调用之后才实例化。

interface IHttpInterceptor {
  intercept(): void;
}
const IHttpInterceptor = createIdentifier<IHttpInterceptor>("http-interceptor");
 
class AuthHttpInterceptor implements IHttpInterceptor {
  intercept(): void {}
}
 
class FileListService {
  constructor(
    @Inject(IHttpInterceptor) private readonly httpI: IHttpInterceptor,
  ) {}
 
  request() {
    this.httpI.intercept();
  }
}
 
const injector = new Injector([
  [FileListService],
  [IHttpInterceptor, { useClass: AuthHttpInterceptor, lazy: true }],
]);

在上面的例子中,FileListService 依赖了 AuthHttpInterceptor,从注入器获取 FileListService 时该类会实例化,但是 AuthHttpInterceptor 并未实例化:

const fileListService = injector.get(FileListService);

只有实际访问了 AuthHttpInterceptor 方法时才会实例化:

fileListService.request(); // -> this.httpI.intercept()

通过延迟实例化部分依赖,可以降低应用初始化时的性能开销。

💡

请确保被延迟实例化的类的构造函数中不存在副作用,否则会导致应用不能按你期望的方式运行。

值作为依赖项

值依赖项是一个符合 ValueDependencyItem 接口的对象:

export interface ValueDependencyItem<T> {
  useValue: T;
}

该值将会直接被提供给依赖它的依赖。

工厂函数依赖项

工厂函数依赖项将实例化的过程重新交还到开发者的手中,这样就有了更高的灵活性,并且它可以像类依赖项一样声明自己的依赖项。

工厂函数依赖项应当是一个符合下面接口的对象:

export interface FactoryDependencyItem<T> {
  useFactory: (...deps: any[]) => T;
  deps?: FactoryDep<any>[];
}

例子:

interface I18NNumberTranspiler {
  transpile(num: number): string
}
const I18NNumberTranspiler = createIdentifier<I18NNumberTranspiler>(
  'i18n-number'
)
 
class ChineseNumberTranspiler implements I18NNumberTranspiler {}
class EnglishNumberTranspiler implements I18NNumberTranspiler {}
 
class I18NService {
  isChinese(): boolean
}
 
const injector = new Injector([
  [I18NService],
  [
    I18NNumberTranspiler,
    {
      useFactory: (i18nService: I18NService) => {
        return i18nService.isChinese()
          ? new ChineseNumberTranspiler()
          : new EnglishNumberTranspiler()
      },
      deps: [I18NService]
    },
  ],
]

已存在的依赖项

有时候你可能需要使用已经存在的依赖项,换句话说,你想要给一个依赖项以别名,这时候你可以使用 ExistingDependencyItem,它的接口如下所示:

export interface ExistingDependencyItem<T> {
  useExisting: DependencyIdentifier<T>;
}

例子:

interface IHttpInterceptor {
  intercept(): void;
}
const IHttpInterceptor = createIdentifier<IHttpInterceptor>("http-interceptor");
 
class AuthHttpInterceptor implements IHttpInterceptor {
  intercept(): void {}
}
 
const INetworkInterceptor = createIdentifier<IHttpInterceptor>(
  "network-interceptor",
);
 
const injector = new Injector([
  [IHttpInterceptor, { useClass: AuthHttpInterceptor, lazy: true }],
  [INetworkInterceptor, { useExisting: IHttpInterceptor }],
]);
 
console.log(
  injector.get(IHttpInterceptor) === injector.get(INetworkInterceptor),
); // true

异步依赖项

有些时候某些依赖并不需要在加载首屏所需 JS 代码的时候加载,因此你可以通过异步依赖项和 webpack 的 import 的函数来懒加载此依赖。此时,该依赖项必须是一个满足 AsyncDependencyItem 的对象:

export interface AsyncDependencyItem<T> {
  useAsync: () => Promise<
    T | Ctor<T> | [DependencyIdentifier<T>, SyncDependencyItem<T>]
  >;
}

useAsync 函数必须要异步返回一个已经实例化的依赖项,或者是异步依赖项之外的其他依赖项。同时,声明依赖关系时,声明的并不是对应的标识符,而是 AsyncHook

例子:

// index.ts
 
interface IReportService {
  report(msg: string): void
}
const IReportService = createIdentifier<IReportService>('report')
 
class OrderService {
  constructor(
    @Inject(IReportService)
    private readonly reportSrvLoader: AsyncHook<IReportService>
  )
 
  public order(): void {
    // ...
    this.reportSrvLoader.whenReady().then((reportSrv) => reportSrv.report(''))
  }
}
 
const injector = new Injector([
  [OrderService],
  [
    IReportService,
    {
      useAsync: () => import('./reportService'),
    },
  ],
])
// reportService.ts
export default class ReportService {
  report(msg: string): void;
}