Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/opensumi/di
A Dependency Injection Library for JavaScript. Support AOP.
https://github.com/opensumi/di
browser dependency-injection ioc nodejs
Last synced: 12 days ago
JSON representation
A Dependency Injection Library for JavaScript. Support AOP.
- Host: GitHub
- URL: https://github.com/opensumi/di
- Owner: opensumi
- License: mit
- Created: 2021-10-11T07:59:06.000Z (over 3 years ago)
- Default Branch: main
- Last Pushed: 2024-03-01T04:12:59.000Z (11 months ago)
- Last Synced: 2024-04-26T05:43:11.957Z (9 months ago)
- Topics: browser, dependency-injection, ioc, nodejs
- Language: TypeScript
- Homepage:
- Size: 250 KB
- Stars: 63
- Watchers: 18
- Forks: 12
- Open Issues: 1
-
Metadata Files:
- Readme: README-zh_CN.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
- awesome-nodejs - @opensumi/di - A dependency injection tool for JavaScript. (Repository / Inversion of control / Dependency Injection (Ioc/DI))
README
# @opensumi/di
这个工具将会帮助你很好的实现依赖反转,而不用关注那些对象实例化的细节。同时,因为对象的实例化在注册器中进行创建,所以工厂模式和单例模式都很容易实现。
## Table of Contents
- [Install](#install)
- [Quick Start](#quick-start)
- [API](#api)
- [Examples](#examples)
- [FAQ](#faq)
- [Related Efforts](#related-efforts)## Install
```sh
npm install @opensumi/di --save
yarn add @opensumi/di
```将您的 tsconfig.json 修改为包含以下设置:
```json
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
```为 Reflect API 添加一个 polyfill(下面的例子使用 reflect-metadata)。你可以使用:
- [reflect-metadata](https://www.npmjs.com/package/reflect-metadata)
- [core-js (core-js/es7/reflect)](https://www.npmjs.com/package/core-js)
- [reflection](https://www.npmjs.com/package/@abraham/reflection)Reflect polyfill 的导入应该只添加一次,并且在使用 DI 之前:
```typescript
// main.ts
import 'reflect-metadata';// 你的代码...
```## Quick Start
让我们从一个简单的例子开始:
```ts
import { Injector } from '@opensumi/di';// 创建一个 Injector,这是一个 IoC 容器
const injector = new Injector();const TokenA = Symbol('TokenA');
injector.addProviders({
token: TokenA,
useValue: 1,
});
injector.get(TokenA) === 1; // true
```The `Injector` class is the starting point of all things. We create a `injector`, and we add a provider into it:
```ts
injector.addProviders({
token: TokenA,
useValue: 1,
});
```We use a `ValueProvider` here, and its role is to provide a value:
```ts
interface ValueProvider {
token: Token;
useValue: any;
}
```We have the following several kinds of the provider. According to the different Provider kinds, Injector will use different logic to provide the value that you need.
```ts
type Provider = ClassProvider | ValueProvider | FactoryProvider | AliasProvider;
```A token is used to find the real value in the Injector, so token should be a global unique value.
```ts
type Token = string | symbol | Function;
```and now we want get value from the `Injector`, just use `Injector.get`:
```ts
injector.get(TokenA) === 1;
```### Providers
这是目前支持的 Provider 类型:
#### ClassProvider
定义一个 Token 使用某个特定的构造函数的时候会用到的 Provider。
```ts
interface ClassProvider {
token: Token;
useClass: ConstructorOf;
}
```在依赖反转之后,构造函数都依赖抽象而不依赖实例的时候会非常有效。比如下面的例子:
```ts
interface Drivable {
drive(): void;
}@Injectable()
class Student {
@Autowired('Drivable')
mBike: Drivable;goToSchool() {
console.log('go to school');
this.mBike.drive();
}
}
```学生对象依赖的是一个可驾驶的交通工具,可以在创建对象的时候提供一个自行车,也可以在创建的时候提供一个汽车:
```ts
@Injectable()
class Car implements Drivable {
drive() {
console.log('by car');
}
}injector.addProviders(Student, {
token: 'Drivable',
useClass: Car,
});const student = injector.get(Student);
student.goToSchool(); // print 'go to school by car'
```#### ValueProvider
This provider is used to provide a value:
```ts
interface ValueProvider {
token: Token;
useValue: any;
}
``````ts
const TokenA = Symbol('TokenA');
injector.addProviders({
token: TokenA,
useValue: 1,
});
injector.get(TokenA) === 1; // true
```#### FactoryProvider
提供一个函数进行对象实例创建的 Provider。
```ts
interface FactoryFunction {
(injector: Injector): T;
}
interface FactoryProvider {
token: Token;
useFactory: FactoryFunction;
}
```同时也提供了一些工厂模式的帮助函数:
1. `asSingleton`
You can implement a singleton factory by using this helper:
```ts
const provider = {
token,
useFactory: asSingleton(() => new A()),
};
```#### AliasProvider
Sets a token to the alias of an existing token.
```ts
interface AliasProvider {
// New Token
token: Token;
// Existing Token
useAlias: Token;
}
```and then you can use:
```ts
const TokenA = Symbol('TokenA');
const TokenB = Symbol('TokenB');
injector.addProviders(
{
token: TokenA,
useValue: 1,
},
{
token: TokenB,
useAlias: TokenA,
},
);
injector.get(TokenA) === 1; // true
injector.get(TokenB) === 1; // true
```### 对构造函数进行注入
在下面这个例子里,你会发现 `class B` 依赖于 `class A`,并且在构造函数的参数列表中声明了这个依赖关系,所以在 `B` 的实例创建过程中,Injector 会自动创建 `A` 的实例,并且注入到 `B` 的实例中。
```ts
@Injectable()
class A {
constructor() {
console.log('Create A');
}
}@Injectable()
class B {
constructor(public a: A) {}
}const injector = new Injector();
injector.addProviders(A, B);const b = injector.get(B); // 打印 'Create A'
console.log(b.a instanceof A); // 打印 'true'
```### 使用 `@Autowired()` 进行动态注入
```ts
@Injectable()
class A {
constructor() {
console.log('Create A');
}
}@Injectable()
class B {
@Autowired()
a: A;
}const injector = new Injector();
injector.addProviders(A, B);const b = injector.get(B);
console.log(b.a instanceof A); // 1. 打印 'Create A', 2. 打印 'true'
```### 可以创建单例或者多例
```ts
@Injectable()
class Singleton {
constructor() {}
}@Injectable({ multiple: true })
class Multiton {
constructor() {}
}const injector = new Injector();
injector.addProviders(Singleton, Multiton);const single1 = injector.get(Singleton);
const single2 = injector.get(Singleton);
console.log(single1 === single2); // print 'true'const multiple1 = injector.get(Multiton);
const multiple2 = injector.get(Multiton);
console.log(multiple1 === multiple2); // print 'false'
```### 类型依赖抽象而不是依赖实现的用法
```ts
const LOGGER_TOKEN = Symbol('LOGGER_TOKEN');interface Logger {
log(msg: string): void;
}@Injectable()
class App {
@Autowired(LOGGER_TOKEN)
logger: Logger;
}@Injectable()
class LoggerImpl implements Logger {
log(msg: string) {
console.log(msg);
}
}const injector = new Injector();
injector.addProviders(App);
injector.addProviders({
token: LOGGER_TOKEN,
useClass: LoggerImpl,
});const app = injector.get(App);
console.log(app.logger instanceof LoggerImpl); // 打印 'true'
```### 使用抽象函数作为 Token 进行依赖注入
```ts
abstract class Logger {
abstract log(msg: string): void;
}@Injectable()
class LoggerImpl implements Logger {
log(msg: string) {
console.log(msg);
}
}@Injectable()
class App {
@Autowired()
logger: Logger;
}const injector = new Injector();
injector.addProviders(App);
injector.addProviders({
token: Logger,
useClass: LoggerImpl,
});const app = injector.get(App);
console.log(app.logger instanceof LoggerImpl); // print 'true'
```## API
### decorator: @Injectable
```ts
interface InstanceOpts {
multiple?: boolean;
}
function Injectable(opts?: InstanceOpts): ClassDecorator;@Injectable({ multiple: true })
class A {}const injector = new Injector([A]);
const a = injector.get(A);
console.log(injector.hasInstance(a)); // print 'false'
```所有需要被 Injector 创建的构造函数都应该使用这个装饰器修饰才可以正常使用,否则会报错。
- multiple: 是否启用多例模式,一旦启用了多例模式之后,Injector 将不会持有实例对象的引用。
### decorator: @Autowired
```ts
function Autowired(token?: Token): PropertyDecorator;@Injectable()
class A {}@Injectable()
class B {
@Autowired()
a: A;
}
```修饰一个属性会被注册器动态创建依赖实例,而这个依赖实例只有在被使用的时候才会被创建出来。比如上面的例子中,只有访问到 `b.a` 的时候,才会创建 A 的实例。
> 需要注意的是,因为 Autowired 依赖着 Injector 的实例,所以只有从 Injector 创建出来的对象可以使用这个装饰器
### decorator: @Inject
```ts
function Inject(token: string | symbol): ParameterDecorator;interface IA {
log(): void;
}@Injectable()
class B {
constructor(@Inject('IA') a: IA) {}
}
```在构造函数进行依赖注入的时候,需要特别指定依赖 Token 的时候的装饰器。当一个构造函数依赖某个抽象,并且这个抽象是在构造函数中传递进来的时候,会需要使用这个装饰器。
### Injector.get
```ts
interface Injector {
get(token: ConstructorOf, args?: ConstructorParameters, opts?: InstanceOpts): TokenResult;
get(token: T, opts?: InstanceOpts): TokenResult;
}
```从 Injector 获取一个对象实例的方法,如果传递的是一个构造函数,第二个参数可以传递构造函数 Arguments 数据,此时将会直接将构造函数创建实例返回,并附加依赖注入的功能,此时的构造函数不需要被 Injectable 装饰也能正常创建对象。例如下面这样:
```ts
@Injectable()
class A {}class B {
@Autowired()
a: A;
}const injector = new Injector([A]);
const b = injector.get(B, []);
console.log(b.a instanceof A); // print 'true'
```### Injector.hasInstance
Whether have an instantiated object in the Injector.
### Injector.disposeOne / Injector.disposeAll
可以使用 `Injector.disposeOne` 和 `Injector.disposeAll` 来释放 Token。
这两个方法会从 DI 容器中删除当前已创建的实例,并尝试调用这个实例的 `dispose` 方法(可以没有)。
### markInjectable
```ts
import { markInjectable } from '@opensumi/di';
import { Editor } from 'path/to/package';markInjectable(Editor);
```You can use this function to mark some Class as Injectable.
## Examples
See More Examples [in the test case](test/use-case.test.ts).
## FAQ
Please see [FAQ.md](docs/faq.md).
## Related Efforts
- [Angular](https://angular.io/guide/dependency-injection) Dependency injection in Angular
- [injection-js](https://github.com/mgechev/injection-js) It is an extraction of the Angular's ReflectiveInjector.
- [InversifyJS](https://github.com/inversify/InversifyJS) A powerful and lightweight inversion of control container for JavaScript & Node.js apps powered by TypeScript.
- [power-di](https://github.com/zhang740/power-di) A lightweight Dependency Injection library.