Angular2的模組架構淺談
引言
angular2相比1引入了更完善的模組系統,回憶ng1的應用中通常在頁面的html標籤或body標籤中新增ng-app節點,值為應用的模組名,整個應用都將圍繞這個模組來展開,到了ng2,模組概念完善了很多,並且再不是由一個模組來統治整個應用(當然非要這麼做也可以),本文就將敘述一番ng2中的模組體系,以及如何統籌ng2模組最終組合成一個完整的應用。
一、根模組、子模組與惰性載入
先說根模組。一個ng2應用至少要有一個根模組,包含ng2自帶的BrowserModule,並宣告為引導模組,在應用啟動時將從此模組展開。
隨著應用的擴大,所有的事情都在一個模組中完成難免會變亂(某種程度上看ng1應用就是這麼做的,並且細分了控制器來拆分應用,這其實浪費了最頂層模組的意義),所以自然而然能想到,可以將系統分為多個模組,每個模組都只做各自的事情而互不干擾,所以進一步的思路就是,用來根模組來載入程式並管理所有子模組(通過路由定向以及為它們提供全域性配置與服務例項),所有的具體業務就交給各個子模組來完成。
然後會有一個問題,那就是既然系統已經被這麼多個子模組瓜分了,並且這些子模組也不可能同時全部都會被使用,也就是隻有其被啟用時才有用,那仍然在應用引導時從根模組載入所有子模組必然會導致效能的浪費以及拖慢執行速度。此時惰性載入模組就派上用場了,將一個子模組定義為惰性載入後,只有在通過路由啟用此模組時才會開始載入此模組(並且ng2甚至支援非同步預載入,即後臺預載入懶載入的模組,這樣當懶載入模組需要被載入時其實其已經載入完成了,又加快了響應速度)。
二、除了模組之外
每個模組都有自己的事情要做,通常包括:
- 引入其他模組 這個在第三部分細說
- 宣告模組包含的元件、指令與管道 所有的元件、指令或管道都必須依附於某個模組,並只在此模組中可用。
- 定義模組提供的服務 服務也有自己所屬的模組,但由於服務是全域性單例的,所以只要在一個模組中提供之後,全域性都通用。注:若多個模組同時提供了服務(通常發生在模組間混亂的import時),一般情況下ng2能識別並只保留一個例項,但在惰性載入的模組中則會發生不可預計的錯誤,所以一定要避免。
- 定義模組將匯出的元件、指令與管道(還可以是此模組引入的模組) 與(1配合使用,同樣在第三部分細說。
三、模組的關聯
模組之間一定要有共享或繼承資源的方式,不然的話每個子模組都必須實現可能用到的全部功能。
比如一個訊息彈窗元件,不可能每個子模組都自己宣告一個訊息元件然後使用,這樣的維護壓力很大且程式碼嚴重浪費。
此時就用到了模組的引入和匯出——
模組A可以引入另一個模組B,然後A就可以使用B中匯出的元件、管道和指令。
當我們要使用系統指令(如ngIf、ngFor等)時,也必須引入系統模組,有一個巧妙的辦法就是實現上圖這樣的共享模組,在引入系統模組並匯出的同時再匯出自定義的其他指令、元件或管道。然後所有引入了此共享模組的子模組就能使用這些系統指令和共享指令了。
有一個基礎的問題是服務需不需要匯出,答案是否定的。服務不需要匯出,因為服務是全域性單例的,一旦被初始化,就已經全域性通用了,相反如果重複的匯入提供了同一服務的模組就可能發生問題:
B提供服務B_S,A匯入了B,C也匯入了B。這種情況下ng2會找到兩處B_S的提供,但ng2尚能夠將其保持在一個例項B_S。但若模組C為惰性載入的模組,在C被建立時,其實會重新初始化一個例項B_S,從C跳轉回到A時,又會建立一個B_S,來回每次跳轉都是如此,結局就會變得混亂不堪。
對於服務更好的做法是寫一個核心模組,專門提供全域性服務,保證此模組只會被根模組引用一次,然後所有的子模組就都已經可以使用這些全域性服務了。
四、ng2模組體系
最後給出一套ng2專案構建的體系,這也是總結歸納了ng2官網的推薦得出的ng2專案的模組體系。
總體思路就是:
> 1.根模組負責全域性的路由。
> 2.核心模組負責全域性服務,也可以定義一些只在根模組中使用的元件等,並只能由根模組引入一次,不再匯出。
> 3.共享模組不做服務的提供,而是定義全域性共享的元件等,以及幫助子模組匯入系統模組,讓子模組只需要匯入此共享模組就夠了。
> 4.子模組內部可以細分自己的子路由到具體的子元件,以及提供自己的服務等。
> 5.除了頁面入口模組(即除了根模組外的具體業務模組)之外的其他子模組均考慮寫成惰性載入的模組,以提升頁面引導的速度減少效能浪費。
> 6.當需要一個比較通用的全域性服務時,可以將其加入CoreModule,也可以再建立一個僅被根模組引入的特性模組。進一步的,甚至可以將此模組釋出到npm,這就需要更強的編碼能力和技術積累了。