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

2022-07-14 10:09 更新

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

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

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

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

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

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

什么時候令牌會被保留

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

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

在一個可能的實(shí)現(xiàn)中,?<lib-card>? 組件使用 ?@ContentChild()? 或者 ?@ContentChildren()? 來獲取 ?<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)在模板中。在這個例子中,?<lib-header>? 沒有用過,你可能期望它會被搖樹優(yōu)化掉,但事實(shí)并非如此。這是因?yàn)?nbsp;?LibCardComponent ?實(shí)際上包含兩個對 ?LibHeaderComponent ?引用。

@ContentChild(LibHeaderComponent) header: LibHeaderComponent;

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

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

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

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

什么時候使用輕量級注入令牌模式

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

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

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

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

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

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

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

使用輕量級注入令牌

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

下例舉例說明了這個 ?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;
}

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

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

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

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

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

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

比如,?LibCardComponent ?現(xiàn)在要查詢 ?LibHeaderToken ?而不是 ?LibHeaderComponent?。這個例子展示了該模式如何讓 ?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();
  }
}

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

為你的輕量級注入令牌命名

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

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


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

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號