Angular 為庫(kù)準(zhǔn)備的輕量級(jí)注入令牌

2022-07-14 10:09 更新

使用輕量級(jí)注入令牌優(yōu)化客戶應(yīng)用的大小

本頁(yè)面會(huì)提供一個(gè)概念性的概述,它介紹了一種建議庫(kù)開發(fā)者使用的依賴注入技術(shù)。使用輕量級(jí)注入令牌設(shè)計(jì)你的庫(kù),這有助于優(yōu)化那些用到你庫(kù)的客戶應(yīng)用的發(fā)布包體積。

你可以使用可搖樹優(yōu)化的提供者來(lái)管理組件和可注入服務(wù)之間的依賴結(jié)構(gòu),以優(yōu)化發(fā)布包體積。這通常會(huì)確保如果提供的組件或服務(wù)從未被應(yīng)用實(shí)際使用過(guò),那么編譯器就可以從發(fā)布包中刪除它的代碼。

但是,由于 Angular 存儲(chǔ)注入令牌的方式,可能會(huì)導(dǎo)致未用到的組件或服務(wù)最終進(jìn)入發(fā)布包中。本頁(yè)描述了依賴注入的一種設(shè)計(jì)模式,它通過(guò)使用輕量級(jí)注入令牌來(lái)支持正確的搖樹優(yōu)化。

這種輕量級(jí)注入令牌設(shè)計(jì)模式對(duì)于庫(kù)開發(fā)者來(lái)說(shuō)尤其重要。它可以確保當(dāng)應(yīng)用只用到了你庫(kù)中的某些功能時(shí),可以從客戶應(yīng)用的發(fā)布包中刪除未使用過(guò)的代碼。

當(dāng)某應(yīng)用用到了你的庫(kù)時(shí),你的庫(kù)中可能會(huì)提供一些客戶應(yīng)用未用到的服務(wù)。在這種情況下,應(yīng)用開發(fā)人員會(huì)期望該服務(wù)是可搖樹優(yōu)化的,不讓這部分代碼增加應(yīng)用的編譯后大小。由于應(yīng)用開發(fā)人員既無(wú)法了解也無(wú)法解決庫(kù)的搖樹優(yōu)化問(wèn)題,因此這是庫(kù)開發(fā)人員的責(zé)任。為了防止未使用的組件被保留下來(lái),你的庫(kù)應(yīng)該使用輕量級(jí)注入令牌這種設(shè)計(jì)模式。

什么時(shí)候令牌會(huì)被保留

為了更好地解釋令牌被保留的條件,我們考慮一個(gè)提供卡片組件的庫(kù),它包含一個(gè)卡片體,還可以包含一個(gè)可選的卡片頭。

<lib-card>
  <lib-header>…</lib-header>
</lib-card>

在一個(gè)可能的實(shí)現(xiàn)中,?<lib-card>? 組件使用 ?@ContentChild()? 或者 ?@ContentChildren()? 來(lái)獲取 ?<lib-header>? 和 ?<lib-body>?,如下所示。

@Component({
  selector: 'lib-header',
  …,
})
class LibHeaderComponent {}

@Component({
  selector: 'lib-card',
  …,
})
class LibCardComponent {
  @ContentChild(LibHeaderComponent)
  header: LibHeaderComponent|null = null;
}

因?yàn)?nbsp;?<lib-header>? 是可選的,所以元素可以用最小化的形式 ?<lib-card></lib-card>? 出現(xiàn)在模板中。在這個(gè)例子中,?<lib-header>? 沒有用過(guò),你可能期望它會(huì)被搖樹優(yōu)化掉,但事實(shí)并非如此。這是因?yàn)?nbsp;?LibCardComponent ?實(shí)際上包含兩個(gè)對(duì) ?LibHeaderComponent ?引用。

@ContentChild(LibHeaderComponent) header: LibHeaderComponent;

  • 其中一個(gè)引用位于類型位置上 - 即,它把 ?LibHeaderComponent ?用作了類型:?header: LibHeaderComponent?;。
  • 另一個(gè)引用位于值的位置 - 即,LibHeaderComponent 是 ?@ContentChild()? 參數(shù)裝飾器的值:?@ContentChild(LibHeaderComponent)?。

編譯器對(duì)這些位置的令牌引用的處理方式也不同。

  • 編譯器在從 TypeScript 轉(zhuǎn)換完后會(huì)刪除這些類型位置上的引用,所以它們對(duì)于搖樹優(yōu)化沒什么影響。
  • 編譯器必須在運(yùn)行時(shí)保留值位置上的引用,這就會(huì)阻止該組件被搖樹優(yōu)化掉。

在這個(gè)例子中,編譯器保留了 ?LibHeaderComponent ?令牌,它出現(xiàn)在了值位置上,這就會(huì)防止所引用的組件被搖樹優(yōu)化掉,即使應(yīng)用開發(fā)者實(shí)際上沒有在任何地方用過(guò) ?<lib-header>?。如果 ?LibHeaderComponent ?很大(代碼、模板和樣式),把它包含進(jìn)來(lái)就會(huì)不必要地大大增加客戶應(yīng)用的大小。

什么時(shí)候使用輕量級(jí)注入令牌模式

當(dāng)一個(gè)組件被用作注入令牌時(shí),就會(huì)出現(xiàn)搖樹優(yōu)化的問(wèn)題。有兩種情況可能會(huì)發(fā)生。

  • 令牌用在內(nèi)容查詢中值的位置上。
  • 該令牌用作構(gòu)造函數(shù)注入的類型說(shuō)明符。

在下面的例子中,兩處對(duì) ?OtherComponent ?令牌的使用導(dǎo)致 ?OtherComponent ?被保留下來(lái)(也就是說(shuō),防止它在未用到時(shí)被搖樹優(yōu)化掉)。

class MyComponent {
  constructor(@Optional() other: OtherComponent) {}

  @ContentChild(OtherComponent)
  other: OtherComponent|null;
}

雖然轉(zhuǎn)換為 JavaScript 時(shí)只會(huì)刪除那些只用作類型說(shuō)明符的令牌,但在運(yùn)行時(shí)依賴注入需要所有這些令牌。這些工作把 ?constructor(@Optional() other: OtherComponent)? 改成了 ?constructor(@Optional() @Inject(OtherComponent) other)?。該令牌現(xiàn)在處于值的位置,并使該搖樹優(yōu)化器保留該引用。

對(duì)于所有服務(wù),庫(kù)都應(yīng)該使用可搖樹優(yōu)化的提供者,在根級(jí)而不是組件構(gòu)造函數(shù)中提供依賴。

使用輕量級(jí)注入令牌

輕量級(jí)注入令牌設(shè)計(jì)模式包括:使用一個(gè)小的抽象類作為注入令牌,并在稍后為它提供實(shí)際實(shí)現(xiàn)。該抽象類固然會(huì)被留下(不會(huì)被搖樹優(yōu)化掉),但它很小,對(duì)應(yīng)用程序的大小沒有任何重大影響。

下例舉例說(shuō)明了這個(gè) ?LibHeaderComponent ?的工作原理。

abstract class LibHeaderToken {}

@Component({
  selector: 'lib-header',
  providers: [
    {provide: LibHeaderToken, useExisting: LibHeaderComponent}
  ]
  …,
})
class LibHeaderComponent extends LibHeaderToken {}

@Component({
  selector: 'lib-card',
  …,
})
class LibCardComponent {
  @ContentChild(LibHeaderToken) header: LibHeaderToken|null = null;
}

在這個(gè)例子中,?LibCardComponent ?的實(shí)現(xiàn)里,?LibHeaderComponent ?既不會(huì)出現(xiàn)在類型的位置也不會(huì)出現(xiàn)在值的位置。這樣就可以讓 ?LibHeaderComponent ?完全被搖樹優(yōu)化掉。?LibHeaderToken ?被留下了,但它只是一個(gè)類聲明,沒有具體的實(shí)現(xiàn)。它很小,并且在編譯后保留時(shí)對(duì)應(yīng)用程序的大小沒有實(shí)質(zhì)影響。

不過(guò),?LibHeaderComponent ?本身實(shí)現(xiàn)了抽象類 ?LibHeaderToken?。你可以放心使用這個(gè)令牌作為組件定義中的提供者,讓 Angular 能夠正確地注入具體類型。

總結(jié)一下,輕量級(jí)注入令牌模式由以下幾部分組成。

  1. 一個(gè)輕量級(jí)的注入令牌,它表現(xiàn)為一個(gè)抽象類。
  2. 一個(gè)實(shí)現(xiàn)該抽象類的組件定義。
  3. 注入這種輕量級(jí)模式時(shí)使用 ?@ContentChild()? 或者 ?@ContentChildren()?。
  4. 實(shí)現(xiàn)輕量級(jí)注入令牌的提供者,它將輕量級(jí)注入令牌和它的實(shí)現(xiàn)關(guān)聯(lián)起來(lái)。

使用輕量級(jí)注入令牌進(jìn)行 API 定義

那些注入了輕量級(jí)注入令牌的組件可能要調(diào)用注入的類中的方法。因?yàn)榱钆片F(xiàn)在是一個(gè)抽象類,并且可注入組件實(shí)現(xiàn)了那個(gè)抽象類,所以你還必須在作為輕量級(jí)注入令牌的抽象類中聲明一個(gè)抽象方法。該方法的實(shí)現(xiàn)代碼(及其所有相關(guān)代碼)都會(huì)留在可注入組件中,但這個(gè)組件本身仍可被搖樹優(yōu)化。這樣就能讓父組件以類型安全的方式與子組件(如果存在)進(jìn)行通信。

比如,?LibCardComponent ?現(xiàn)在要查詢 ?LibHeaderToken ?而不是 ?LibHeaderComponent?。這個(gè)例子展示了該模式如何讓 ?LibCardComponent ?與 ?LibHeaderComponent ?通信,卻不用實(shí)際引用 ?LibHeaderComponent?。

abstract class LibHeaderToken {
  abstract doSomething(): void;
}

@Component({
  selector: 'lib-header',
  providers: [
    {provide: LibHeaderToken, useExisting: LibHeaderComponent}
  ]
  …,
})
class LibHeaderComponent extends LibHeaderToken {
  doSomething(): void {
    // Concrete implementation of `doSomething`
  }
}

@Component({
  selector: 'lib-card',
  …,
})
class LibCardComponent implement AfterContentInit {
  @ContentChild(LibHeaderToken)
  header: LibHeaderToken|null = null;

  ngAfterContentInit(): void {
    this.header && this.header.doSomething();
  }
}

在這個(gè)例子中,父組件會(huì)查詢令牌以獲取子組件,并持有結(jié)果組件的引用(如果存在)。在調(diào)用子組件中的方法之前,父組件會(huì)檢查子組件是否存在。如果子組件已經(jīng)被搖樹優(yōu)化掉,那運(yùn)行期間就沒有對(duì)它的引用,當(dāng)然也沒有調(diào)用它的方法。

為你的輕量級(jí)注入令牌命名

輕量級(jí)注入令牌只對(duì)組件有用。Angular 風(fēng)格指南中建議你使用“Component”后綴命名組件。比如“LibHeaderComponent”就遵循這個(gè)約定。

為了維護(hù)組件及其令牌之間的對(duì)應(yīng)關(guān)系,同時(shí)又要區(qū)分它們,推薦的寫法是使用組件基本名加上后綴“?Token?”來(lái)命名你的輕量級(jí)注入令牌:“?LibHeaderToken?”。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)