Skip to content

Commit

Permalink
feat: 使用多种 Provider,灵活注入对象
Browse files Browse the repository at this point in the history
  • Loading branch information
thinkasany committed Oct 23, 2023
1 parent 5fe8345 commit fda5a9a
Show file tree
Hide file tree
Showing 2 changed files with 291 additions and 0 deletions.
290 changes: 290 additions & 0 deletions docs/nest/7/1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
[源码](https://github.com/thinkasany/nestjs-course-code)

[掘金](https://juejin.cn/book/7226988578700525605/section/7227379507152781349)

> 这一章主要是讲的如何在DI里为依赖提供注入
>
> Nest 实现了 IOC 容器,会从入口模块开始扫描,分析 Module 之间的引用关系,对象之间的依赖关系,自动把 provider 注入到目标对象。
```
Provide 注入写法:
1. 简写方式
2.useValue
1. useClass
4.useExisting
5.useFactory
```

# provider 一般都是用 @Injectable 修饰的 class:

```
@Injectable()
export class AppService {
getHello(): string {
return 'Hello World!';
}
}
```

在 Module 的 providers 里声明:

```
@Module({
imports: [],
controllers: [AppController],
providers: [AppService]
```

其实这是一种简写,完整的写法是这样的:

```
providers: [
{
provide: AppService,
useClass: AppService,
},
],
```

# 通过 provide 指定注入的 token,通过 useClass 指定注入的对象的类,Nest 会自动对它做实例化再注入。

```
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
}
```

# 如果不想用构造器注入,也可以属性注入:

```
@Controller()
export class AppController {
@Inject(AppService)
private readonly appService: AppService;
@Get()
getHello(): string {
return this.appService.getHello();
}
}
```

通过 @Inject 指定注入的 provider 的 token 即可。

# 当然,这个 token 也可以是字符串:

```
providers: [
{
provide: 'app_service',
useClass: AppService,
},
],
```

如果 token 是字符串的话,注入的时候就要用 @Inject 手动指定注入对象的 token 了:

```
@Controller()
export class AppController {
@Inject('app_service')
private readonly appService: AppService;
@Get()
getHello(): string {
return this.appService.getHello();
}
}
```

相比之下,用 class 做 token 可以省去 @Inject,比较简便。

# 除了指定 class 外,还可以直接指定一个值,让 IOC 容器来注入。

```
providers: [
AppService,
{
provide: 'person',
useValue: {
name: 'aaa',
age: 20,
},
},
],
```

使用 provide 指定 token,使用 useValue 指定值。

然后在对象里注入它:

```
@Controller()
export class AppController {
@Inject(AppService)
private readonly appService: AppService;
@Inject('person')
private readonly person: { name: string; age: number };
@Get()
getHello(): string {
console.log(this.person);
return this.appService.getHello();
}
}
```

# provider 的值可能是动态产生的,Nest 也同样支持:

```
providers: [
AppService,
{
provide: 'person2',
useFactory() {
return {
name: 'bbb',
desc: 'cccc',
};
},
},
],
```

使用 useFactory 来动态创建一个对象。

在对象里注入:

```
@Controller()
export class AppController {
@Inject(AppService)
private readonly appService: AppService;
@Inject('person2')
private readonly person2: { name: string; desc: string };
@Get()
getHello(): string {
console.log(this.person2);
return this.appService.getHello();
}
}
```

这个 useFactory 也是支持参数的注入的:

```
providers: [
AppService,
{
provide: 'person',
useValue: {
name: 'aaa',
age: 20,
},
},
{
provide: 'person3',
useFactory(person: { name: string }, appService: AppService) {
return {
name: person.name,
desc: appService.getHello(),
};
},
inject: ['person', AppService],
},
],
```

```
@Controller()
export class AppController {
@Inject(AppService)
private readonly appService: AppService;
@Inject('person')
private readonly person: { name: string; age: number };
@Inject('person3')
private readonly person3: { name: string; desc: string };
@Get()
getHello(): string {
console.log(this.person3);
return this.appService.getHello();
}
}
```

```
ication successfully started +1ms
{ name: 'aaa', desc: 'Hello World!' }
```

通过 inject 声明了两个 token,一个是字符串 token 的 person,一个是 class token 的 AppService。

在调用 useFactory 方法的时候,Nest 就会注入这两个对象:

# useFactory 支持异步:

```
providers: [
AppService,
{
provide: 'person5',
async useFactory() {
await new Promise((resolve) => {
setTimeout(resolve, 5000);
});
return {
name: 'bbb',
desc: 'cccc',
};
},
},
],
```

# 此外,provider 还可以通过 useExisting 来指定别名:

```
{
provide: 'person4',
useExisting: 'person2',
},
```

这里就是给 person2 的 token 的 provider 起个新的 token 叫做 person4。

然后就可以用新 token 来注入了

这些自定义 provider 的方式里,最常用的是 useClass,不过我们一般会用简写,也就是直接指定 class。

useClass 的方式由 IOC 容器负责实例化,我们也可以用 useValue、useFactory 直接指定对象。

useExisting 只是用来起别名的,有的场景下会用到。

比如 @nestjs/typeorm 里就用到了 useValue、useFactory、useExisting

用 useFactory 根据传入的 options
[动态创建数据库连接对象:](https://github.com/nestjs/typeorm/blob/153da09a384fdbf797b66e4500b69a72a7a47b78/lib/typeorm-core.module.ts#L83-L101)

用 useExisting 给 DataSource 起了一个 Connection

# 总结

一般情况下,provider 是通过 @Injectable 声明,然后在 @Module 的 providers 数组里注册的 class。

默认的 token 就是 class,这样不用使用 @Inject 来指定注入的 token。

但也可以用字符串类型的 token,不过注入的时候要用 @Inject 单独指定。

除了可以用 useClass 指定注入的 class,还可以用 useValue 直接指定注入的对象。

如果想动态生成对象,可以使用 useFactory,它的参数也注入 IOC 容器中的对象,然后动态返回 provider 的对象。

如果想起别名,可以用 useExisting 给已有的 token,指定一个新 token。

灵活运用这些 provider 类型,就可以利用 Nest 的 IOC 容器中注入任何对象。
1 change: 1 addition & 0 deletions docs/summary.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
- Nest 通关秘籍
- [前言](/docs/nest/index.md)
- [5.IOC 解决了什么痛点问题?](/docs/nest/5/5.md)
- [7.使用多种 Provider,灵活注入对象](/docs/nest/7/1.md)
- [24.Nest 如何使用 multer 实现文件上传](/docs/nest/24/24.md)
- [32.SQL 查询语句的所有语法和函数](/docs/nest/32/32.md)
- [33.一对一、join 查询、级联方式](/docs/nest/33/33.md)
Expand Down

0 comments on commit fda5a9a

Please sign in to comment.