这些年,总有人跟我说:「雪狼,Angular 的 DI 我懂,不就是 constructor(private service: MyService) 嘛,简单!」

每当这时,我都会笑一笑,告诉他:「兄弟,你看到的,只是冰山一角,是 DI 的『形』。它真正的威力,那掌控应用血脉、让你拥有上帝般创造力的部分,藏在水面之下。那,才是它的『神』。」

今天,我就带你潜入深水,一同探寻 DI 的「神」之领域。

DI 之「神」:非「扁平江湖」,实乃「层级王朝」#

多数人对 DI 最大的误解,就是以为它只是一个扁平的、全局的「服务中心」。大错特错!

Angular 的注入器体系,不是一个混沌的江湖,而是一个等级森严、与你的组件树一一对应的「层级王朝」。

文生图:一棵发光的树,树根是RootInjector,树干是AppModule的Injector,每个树枝和树叶(代表组件)上都有一个更小的、发光的节点,代表它们自己的Injector。一个请求(光点)从一片叶子发出,沿着树枝、树干,一直向上追溯到树根。风格:数字、科技感。

这个王朝的法则很简单,就像古代官员「寻找上级」:当一个组件(地方官员)需要一个依赖(一道批文)时,它先看自己的「衙门」(自身注入器)有没有。没有?那就沿着组件树向上,去「州府」(父组件注入器)找,再没有,就一路找到「京城」(根注入器)。

这个「层级」设计,就是 DI 的「神魂」所在。它赋予了你精细化控制服务实例生命周期的无上权力:

  • 根注入器:通过 @Injectable({ providedIn: 'root' }) 注册的服务,是王朝的「中央军」,全局唯一,贯穿始终。

  • 路由注入器:在惰性加载路由中提供的服务,是镇守一方的「节度使」,只在进入其辖区时才上任。

  • 组件注入器:在组件 providers 数组中提供的服务,是组件的「私家护卫」,每个组件实例都会配备一套全新的卫队。

DI 之「法」:四道「圣旨」与 inject() 的「王炸」#

providers 数组可不是简单的名单,它是皇帝颁发不同依赖的四种「圣旨」,每一种都有其妙用。

  1. useClass (偷天换日){ provide: Logger, useClass: BetterLogger }。这道圣旨说:「凡有索要 Logger 者,赐 BetterLogger!」 这是实现「面向接口编程」的无上法门。

  2. useValue (开箱即食){ provide: APP_CONFIG, useValue: {...} }。这道圣旨最直接:「把这个朕亲手打包好的 APP_CONFIG 对象,直接发下去!」

  3. useFactory + inject() (炼丹术):这是最强的「王炸」组合。useFactory 相当于一道命令:「宣『炼丹宗师』上殿,为朕现场炼制此物!」 而现代的 inject() 函数,则让这位宗师拥有了在炼丹过程中,随时从天地间(当前注入上下文)汲取灵气(其他依赖)的通天神力。

    
    // 现代炼丹术
    
    {
    
      provide: ApiService,
    
      useFactory: () => {
    
        const http = inject(HttpClient); // 现场吸取「HttpClient」灵气
    
        const config = inject(APP_CONFIG); // 现场吸取「APP_CONFIG」灵气
    
        // ... 可加入任意复杂的判断逻辑,炼制不同神丹 ...
    
        return new ApiService(http, config.apiEndpoint);
    
      }
    
      // deps 数组这个「拐杖」已经不再需要了!
    
    }
  4. useExisting (赐马甲){ provide: OldLogger, useExisting: NewLogger }。这道圣旨用于安抚前朝老臣:「还有用旧令牌 OldLogger 的,别给他另起炉灶了,让他去隔壁 NewLogger 家领俸禄吧。」 这是重构时的「维稳」神技。

DI 之「术」:更优雅的「寻访」方式#

在「王朝」中寻找上级,还有一些「微操」,比如「只能问同级(@Self)」、「可以问不到(@Optional)」等。在 inject() 时代,这些「寻访」规则的表达也更优雅了。

过去,像一堆挂饰,叮叮当当

class SomeClass {
    constructor(@Optional() @Self() private logger?: Logger) {
    }
}

现在,像一份清晰的公文

class SomeClass {
    private logger = inject(Logger, {
        optional: true, // 相当于 @Optional()
        self: true,     // 相当于 @Self()
    });
}

将寻访规则作为 inject 函数的第二个参数,意图更集中,代码更清爽。

结语:形而上者谓之道#

《易经》有云:

形而上者谓之道,形而下者谓之器。

(超越具体形态的规律、思想,称之为「道」;具备具体形态的工具、事物,称之为「器」。)

如果说,你过去只是把 DI 当做一个方便获取服务实例的「器」,那么希望今天之后,你能悟到它背后那关于「层级」、「秩序」、「创造」的「道」。

在独立组件的时代,DI 的重要性不降反升。它不再仅仅是后勤,它就是连接万千「独立王国」的「丝绸之路」与「神经中枢」。当你开始思考「这个服务应该由路由提供,还是组件自己提供?」、「我能否用 useFactory 动态替换掉某个依赖?」时,恭喜你,你已经不再是一个只会用剑的「兵」,而开始拥有「铸剑师」的智慧了。