{"id":21597728,"url":"https://github.com/tbhuabi/di","last_synced_at":"2025-07-20T15:02:30.897Z","repository":{"id":45746375,"uuid":"318565351","full_name":"tbhuabi/di","owner":"tbhuabi","description":" 依赖注入库","archived":false,"fork":false,"pushed_at":"2022-11-23T11:55:25.000Z","size":109,"stargazers_count":24,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-06-17T13:52:46.647Z","etag":null,"topics":["dependency-injection"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tbhuabi.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-12-04T15:59:49.000Z","updated_at":"2024-12-12T02:45:55.000Z","dependencies_parsed_at":"2023-01-22T00:16:23.850Z","dependency_job_id":null,"html_url":"https://github.com/tbhuabi/di","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/tbhuabi/di","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tbhuabi%2Fdi","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tbhuabi%2Fdi/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tbhuabi%2Fdi/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tbhuabi%2Fdi/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tbhuabi","download_url":"https://codeload.github.com/tbhuabi/di/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tbhuabi%2Fdi/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266143940,"owners_count":23883067,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dependency-injection"],"created_at":"2024-11-24T18:09:51.554Z","updated_at":"2025-07-20T15:02:30.735Z","avatar_url":"https://github.com/tbhuabi.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"依赖注入库 - DI\n==================================================================\n\nIoC（Inversion of Control）控制反转，是面向对象编程中的一种设计原则，用来降低计算机代码之间的耦合度，而 DI 则是实现IOC的一种实现技术，简单来说就是我们将依赖注入给调用方，而不需要调用方来主动获取依赖。  \n\n\n## 安装\n```\nnpm install @tanbo/di reflect-metadata\n```\n\n## 基本使用\n\n要实现依赖注入，首先需要用 `Injectable` 标识一个类是可注入的，再把这个类添加到 `ReflectiveInjector` 容器（也可以叫注入器）内，然后通过 `ReflectiveInjector` 实例的 `get` 方法，就可以获取容器内，任意类的实例了。\n\n```typescript\nimport 'reflect-metadata';\nimport { Injectable, ReflectiveInjector } from '@tanbo/di';\n\n// 声明类是可注入的\n@Injectable()\nclass Child {\n  name = 'child'\n  index: number\n  constructor() {}\n}\n\n// 声明类是可注入的\n@Injectable()\nclass Parent {\n  name = 'parent'\n  constructor(public child: Child) {\n  }\n}\n\n// 创建容器\nconst injector = new ReflectiveInjector(null, [Parent, Child]);\n\n// 获取实例\nconst instance = injector.get(Parent);\nconsole.log(instance);\n```\n\n## 如何注入其它数据\n\n在实际应用中，很多时候不仅仅只需要注入类的实例，可能还需要注入其它数据，但通过 Typescript 自动解析元数据，是无法获取到相关依赖信息的。这时，就需要通过指定 token 的方式实现。\n\n```typescript\nimport { Injectable, InjectionToken, Inject, ReflectiveInjector } from '@tanbo/di';\n\ninterface UserInfo {\n  name: string;\n}\n\nconst UserInfoInjectionToken = new InjectionToken\u003cUserInfo\u003e('UserInfoInjectionToken');\n\n@Injectable()\nclass User {\n  constructor(@Inject(UserInfoInjectionToken) public userInfo: UserInfo) {\n  }\n}\n\nconst injector = new ReflectiveInjector(null, [\n  User, {\n    provide: UserInfoInjectionToken,\n    useValue: {\n      name: '张三'\n    }\n  }\n]);\n\nconst instance = injector.get(User);\n```\n\n## 多级注入器\n\n在简单的场景中，一个容器就可以满足我们的需求，但当应用越来越复杂时，我们就需要根据不同的作用域，来实现访问控制了。\n\n比如，我们希望全局有一些基础的类，在任意类都能注入，还有另外一些类，会根据用户角色的不同，只能访问特定的类。如下图，我们把图中所有的小方块当作类，它们的访问关系是这样的。\n\n![](./_source/tree.png)\n\n如图所示，每个类都可以访问当前容器的兄弟类或上层容器的类。在实际的代码组织中，我们只需要给一个容器指定一个父容器，就可以了。\n\n```typescript\nimport { ReflectiveInjector } from './reflective-injector';\n\nconst rootInjector = new ReflectiveInjector(null, [\n  网络,\n  用户\n])\n\nconst middleInjector1 = new ReflectiveInjector(rootInjector, [\n  员工列表,\n  薪资明细\n])\n\nconst middleInjector2 = new ReflectiveInjector(rootInjector, [\n  开发规则,\n  服务器\n])\n\nconst leafInjector1 = new ReflectiveInjector(middleInjector2, [\n  前端开发,\n  JS代码仓库\n])\n\nconst leafInjector2 = new ReflectiveInjector(middleInjector2, [\n  后台开发,\n  GO代码仓库\n])\n```\n\n通过如上的代码组织，我们实现了示例图中的注入树结构。那么 `前端开发` 这个类，可以注入哪些类呢？如前面文档所述，可以注入以下类：\n\n![](./_source/tree1.png)\n\n## Provider 示例\n\n在上面的示例中，我们看到了我们不但可以注入类的实例，也可以通过 `useValue` 的方式，直接注入一个值。使用 `useValue` 的这种方式，我们叫做 ValueProvider。在 @tanbo/di 中，我们还有更多方法来提供一个可注入的对象。\n\n### useValue\n\n`ValueProvider` 可以让我们把任意值注入到类中，常用在一些全局配置对象，或需要注入非类实例的对象或数据的场景。\n\n```typescript\nimport { Injectable, InjectionToken, Inject, ReflectiveInjector } from '@tanbo/di';\n\nconst PathInjectionToken = new InjectionToken\u003cstring\u003e('PathInjectionToken')\n\n@Injectable()\nclass HttpExample {\n  constructor(@Inject(PathInjectionToken) public path: string) {\n  }\n}\n\nconst injector = new ReflectiveInjector(null, [\n  HttpExample,\n  {\n    provide: PathInjectionToken,\n    useValue: '/api'\n  }\n])\n\nconst http = injector.get(HttpExample)\nconsole.log(http)\n```\n\n### classProvider\n\n`classProvider` 可以让我们用一个类来替换另一个类，通常是原程序的注入参数依赖于一个抽象类，再由我们提供一个实现类去实现具体的逻辑和功能。\n\n```typescript\n// 原程序\nimport { Injectable, InjectionToken, Inject, ReflectiveInjector } from '@tanbo/di';\n\n@Injectable()\nabstract class Dialog {\n  abstract show(el: Element): Promise\u003cboolean\u003e\n}\n\n@Injectable()\nclass User {\n  constructor(private dialog: Dialog) {\n  }\n  \n  login() {\n    const dialogElement = document.createElement('div')\n    // dialog ui 及交互实现...\n    this.dialog.show(dialogElement).then(isLogin =\u003e {\n      console.log(isLogin)\n    })\n  }\n}\n```\n\n```typescript\n// 实现 Dialog\nimport { Injectable, InjectionToken, Inject, ReflectiveInjector } from '@tanbo/di';\n\n@Injectable()\nclass MyDialog implements Dialog {\n  show(el: Element): Promise\u003cboolean\u003e {\n    let isLogin = false\n    // dialog 逻辑实现...\n    \n    return Promise.resolve(isLogin)\n  }\n}\n\nconst injector = new ReflectiveInjector(null, [\n  User,\n  {\n    provide: Dialog,\n    useClass: MyDialog\n  }\n])\n\nconst user = injector.get(User)\nuser.login()\n```\n\n### useFactory\n\n`FactoryProvider` 可以让我们使用一个函数的返回值当作注入参数，注入到类中。\n\n```typescript\n@Injectable()\nclass Http {\n  constructor(private config: HttpConfig) {\n  }\n}\n\nconst injector = new ReflectiveInjector(null, [\n  Http,\n  {\n    provide: HttpConfig,\n    useFactory() {\n      return {\n        baseURI: '/api',\n        timeout: 5000\n      }\n    }\n  }\n])\n```\n\n如果 `useFactory` 参数的内部依赖于其它类，我们还可以通过声明 `deps` 依赖参数数组，让注入器在调用时，自动把相关的依赖传进来。\n```typescript\n@Injectable()\nclass Http {\n  constructor(private config: HttpConfig) {\n  }\n}\n\nconst injector = new ReflectiveInjector(null, [\n  App,\n  Http,\n  {\n    provide: HttpConfig,\n    useFactory(app: App) {\n      return {\n        baseURI: app.isTest ? '/test/api' : '/api',\n        timeout: 5000\n      }\n    },\n    deps: [App]\n  }\n])\n```\n\n## 依赖注入规则声明\n\n我们除了可以在类的构造函数中，注入一个另一个类，我们还可以同时声明，让注入器按照一定的规则来注入，以适应更多的场景。\n\n### Optional\n\n如果我们的注入参数是可选的，我们可以声明 Optional 来让注入器在注入器树上找不到相关依赖时，并不抛出异常，而是注入一个 null。\n\u003e 这种场景特别适合那种我们提供一个最基础的能力，如果用户有更好的，我们就用更好的。\n\n```typescript\n@Injectable()\nclass Example {\n  constructor(@Optional() private http: Http) {\n    if (!this.http) {\n      this.http = new XMLHttpRequest()\n    }\n  }\n}\n```\n\n### Self\n\n由于注入器是根据就近原则，依次在注入器树上查找相关类实例的，如果我们想要注入器在查找时，锁定在当前容器内查找，则可以通过 Self 装饰器来声明：\n\n```typescript\n@Injectable()\nclass Example {\n  constructor(@Self() private http: Http) {\n  }\n}\n```\n\n### SkipSelf\n\n当然，如果查找时，你不想从当前容器内查找，而是想要从上一层容器开始查找，则可以通过 SkipSelf 装饰器来声明：\n\n```typescript\n@Injectable()\nclass Example {\n  constructor(@SkipSelf() private http: Http) {\n  }\n}\n```\n\n### Inject\n\n你还可以通过 Inject 装饰器指定注入 token，而不是通过参数类型。\n\n```typescript\n@Injectable()\nclass Example {\n  constructor(@Inject(MyHttp) private http: Http) {\n  }\n}\n```\n\n当模块引用关系复杂时，我们需要注入的类，会在我们当前类后面才声明，这时会导致当前类在获取元数据时，拿不到类型，这时，我们也可以通过如下方式解决：\n\n```typescript\n@Injectable()\nclass Example {\n  constructor(@Inject(forwardRef(() =\u003e Http)) private http: Http) {\n  }\n}\n```\n\n\u003e 需要说明的是，你可以自由组合上面的装饰器。而不仅限于一个。\n\n### \n\n## deps 参数详解\n\n在上面的示例中，我们看到了在使用 `FactoryProvider` 时，可以用 `deps` 声明依赖参数。实际上，在 `ClassProvder`、`ConstructorProvider` 中，一样可以使用 `deps` 声明依赖参数，只不过，当你不声明时，注入器会根据 `ClassProvder` 和 `ConstructorProvider` 的元数据，自动帮我们做了。\n\n由于 `useFactory` 是一个工厂函数，注入器并不能获取到其参数的依赖元数据，所以，当使用 `useFactory` 时，如果我们想有参数，我们是一定要声明依赖参数的。\n\n### 一般场景下的依赖声明\n\n普通类声明\n```typescript\nconst injector = new ReflectiveInjector(parentInjector, [\n  Http,\n  User,\n  {\n    provide: Example,\n    useFactory(http: Http, user: User) {\n      return new MyExample(http, user)\n    },\n    deps: [Http, User] // 按参数顺序声明依赖\n  }\n])\n```\n\n通过 InjectionToken 查找\n\n```typescript\n// const ConfigInjectionToken = new InjectionToken\u003cConfig\u003e('ConfigInjectionToken')\nconst injector = new ReflectiveInjector(parentInjector, [\n  {\n    provide: ConfigInjectionToken,\n    useValue: {\n      baseURI: '/api'\n    }\n  },\n  {\n    provide: Example,\n    useFactory(config: Config) {\n      return new MyExample(config)\n    },\n    deps: [HttpInjectionToken]\n  }\n])\n```\n\n### 定制参数的查询规则\n\n当参数可选时\n```typescript\nconst injector = new ReflectiveInjector(parentInjector, [\n  User,\n  {\n    provide: Example,\n    useFactory(http: Http, user: User) {\n      // 这里 http 可能为 null\n      return new MyExample(http, user)\n    },\n    deps: [\n      [Http, new Optional()], 声明 Http 可以是可选的\n      User\n    ]\n  }\n])\n```\n\n跳过当前容器，向上查找\n```typescript\nconst injector = new ReflectiveInjector(parentInjector, [\n  Http,\n  User,\n  {\n    provide: Example,\n    useFactory(http: Http, user: User) {\n      // 这里 http 实例为 parentInjector 查找出来的，而不是当前的 injector 容器\n      return new MyExample(http, user)\n    },\n    deps: [\n      [Http, new SkipSelf()], // 声明 Http 可以是可选的\n      User\n    ]\n  }\n])\n```\n\n锁定当前容器\n```typescript\nconst injector = new ReflectiveInjector(parentInjector, [\n  Http,\n  User,\n  {\n    provide: Example,\n    useFactory(http: Http, user: User) {\n      // 这里 http 实例只在当前容器内查找\n      return new MyExample(http, user)\n    },\n    deps: [\n      [Http, new Self()], // 声明 Http 只能在当前容器查找\n      User\n    ]\n  }\n])\n```\n\n在上面的参数声明中，如果有查询规则定制，我们会把单个参数的依赖和规则放在一个数组里，如这样：\n\n```typescript\nconst injector = new ReflectiveInjector(parentInjector, [\n  {\n    provide: Example,\n    useFactory(http: Http) {\n\n    },\n    deps: [\n      [Http, new Optional()] // 单个参数查询规则定制\n    ]\n  }\n])\n```\n需要说明的是，数组内的顺序是无关紧要的，同时还可以添加多个规则，如这样：\n```typescript\nconst injector = new ReflectiveInjector(parentInjector, [\n  {\n    provide: Example,\n    useFactory(http: Http) {\n\n    },\n    deps: [\n      [new Optional(), Http, new SkipSelf(), ] // 多个规则，且顺序无关\n    ]\n  }\n])\n```\n\n## Injector.get\n\n在获取实例时，我们会通过 injector.get 方法获取，如下：\n\n```typescript\nconst injector = new ReflectiveInjector(parentInjector, [\n  Http,\n  User\n])\n\nconst http = injector.get(Http)\n```\n其实，我们在前面的文档中，声明的各种查询规则，最终都是调用 injector.get 方法来实现的。这就需要我们组合传入后面的两个参数。\n\n当我们要获取的依赖是可选择时：\n\n```typescript\n// 如果当前容器没有 Http，在获取实例时，会抛出异常，但如果我们给了第二个参数，则会把第二个参数当作没有查找到结果时的返回值，且不会抛出异常\nconst http = injector.get(Http, null)\n```\n\n我们还可以通过 injector.get 方法的第三个参数，设置查询规则。\n\n```typescript\nimport { InjectFlags } from '@tanbo/di';\n\n// 设置跳过当前的容器查询\nconst http = injector.get(Http, null, InjectFlags.SkipSelf)\n```\n\nInjectFlags 的查询规则如下：\n```typescript\nexport enum InjectFlags {\n  /** 默认查找规则 */\n  Default = 'Default',\n  /** 锁定当前容器 */\n  Self = 'Self',\n  /** 跳过当前容器 */\n  SkipSelf = 'SkipSelf',\n  /** 可选查找 */\n  Optional = 'Optional'\n}\n```\n\n## 容器作用域\n\n在异步场景中，如果我们不想异步的类提前在容器内声明，可以通过 `scope` 的方式异步提供到指定容器。\n\n创建 scope\n```typescript\nimport { Scope } from '@tanbo/di';\n\nexport const scope = new Scope('myScope')\n```\n\n```typescript\n// # parent-injector.ts 同步加载的文件\nimport { scope } from './my-scope'\n\nexport const parentInjector = new ReflectiveInjector(null, [/* 你的同步类 */], scope) // 声明当前容器为一个具名的的 scope\n```\n\n在异步文件中，添加 scope 声明：\n```typescript\nimport { scope } from './my-scope'\n\n@Injectable({\n  provideIn: scope\n})\nexport class Example {}\n```\n\n```typescript\n// 异步加载的文件\nimport { scope } from './my-scope'\nimport { parentInjector } from './parent-injector'\nimport { Example } from './example'\n\nconst asyncInjector = new ReflectiveInjector(parentInjector, [])\n\nconst example = asyncInjector.get(Example)\n```\n\n由于 Example 类的装饰器中，我们提供了 `provideIn` 的作用域声明。所以在异步的容器中，我们可以不在当前容器内再次声明。在调用异步容器的 get 方法时，会自动把 Example 类添加到声明了相同作用域的父容器内。\n需要注意的是，查找还是按照就近原则，如果在当前容器到声明了 scope 的容器的注入器树之间提供了声明了相同的 provide，那么，你得到实例的将不是 scope 容器内的实例，而是最近的那一个。\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftbhuabi%2Fdi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftbhuabi%2Fdi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftbhuabi%2Fdi/lists"}