溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

淺析Angular2子模塊以及異步加載

發(fā)布時(shí)間:2020-09-12 01:01:05 來(lái)源:腳本之家 閱讀:193 作者:Coding I am 欄目:web開(kāi)發(fā)

用Angular2開(kāi)發(fā)一個(gè)大型的應(yīng)用,我們通常都需要分模塊進(jìn)行開(kāi)發(fā)。例如將某一個(gè)功能的相關(guān)頁(yè)面和功能放在一個(gè)模塊里面,這樣既可以實(shí)現(xiàn)系統(tǒng)的松耦合,給開(kāi)發(fā)和后期的維護(hù)帶來(lái)很大的便利。同時(shí),對(duì)于子模塊,我們還可以使用延時(shí)加載,這樣可以減少初始加載的文件的大小。在這篇文章中,我們就來(lái)看看在Angular2框架下怎么實(shí)現(xiàn)子模塊及其延時(shí)加載。

可以在這里查看本文使用的實(shí)例 。該實(shí)例基于上篇文章Angular2使用Guard和Resolve進(jìn)行驗(yàn)證和權(quán)限控制 所用的實(shí)例,并在它基礎(chǔ)上添加了一個(gè)lazy的模塊,以及將現(xiàn)有的todo模塊配置成延時(shí)加載方式。

為了體現(xiàn)啟用延時(shí)加載前后的包的大小變化,以及啟用壓縮后的變化,在這個(gè)教程里面,使用了angular-cli創(chuàng)建項(xiàng)目腳手架,并用它來(lái)進(jìn)行測(cè)試和打包。有關(guān)angular-cli的使用請(qǐng)查看 官網(wǎng) 。在這篇文章我們使用的angular-cli的版本是1.0.0-beta.21。如果你使用的是別的版本,可能結(jié)果就會(huì)不一樣。甚至有些錯(cuò)誤,我們?cè)谧詈髸?huì)說(shuō)明當(dāng)前版本angular-cli的bug。

模塊設(shè)計(jì)

在開(kāi)發(fā)Angular2應(yīng)用時(shí),像組件設(shè)計(jì)、路由設(shè)計(jì)以外,對(duì)于一個(gè)較大型的應(yīng)用,我們還需要設(shè)計(jì)模塊。例如,將一個(gè)應(yīng)用分成幾個(gè)功能模塊,以及有哪些公用模塊。公用模塊里面應(yīng)該放公用的service類(lèi),例如權(quán)限驗(yàn)證、登錄、獲取用戶(hù)信息、全局的錯(cuò)誤處理、工具類(lèi)等,還有封裝的指令或組件。而在某一個(gè)功能模塊里面,只處理這個(gè)模塊里面的業(yè)務(wù),盡量不和其他模塊交互。

拿之前教程中的TodoList應(yīng)用來(lái)說(shuō),只有home頁(yè)面和2個(gè)todo頁(yè)面,我們把todo相關(guān)的功能放在一個(gè)子模塊里面,為了演示,又加了一個(gè)簡(jiǎn)單的名字叫l(wèi)azy的模塊。我們將把todo模塊和lazy模塊配置成延時(shí)加載的模塊。

子模塊開(kāi)發(fā)

接下來(lái)再看看子模塊的開(kāi)發(fā)。其實(shí)在之前的例子中,就把todo相關(guān)的組件放在了一個(gè)模塊里面。但是卻沒(méi)有強(qiáng)調(diào)子模塊開(kāi)發(fā)需要注意的地方,甚至有些配置可能沒(méi)有采用子模塊的方式進(jìn)行配置。這里,我們就主要說(shuō)明一下需要注意的地方,如果要查看完整的代碼,請(qǐng)參考 實(shí)例源代碼 。

子模塊路由

首先需要注意的是路由。在之前的例子中,我們把todo相關(guān)的路由定義在一個(gè)文件中,然后在app的路由定義中把所有路由合并到一起。 todo.routes.ts 的內(nèi)容如下:

// 省略import
export const TodoRoutes: Route[] = [
  {
    path: 'todo',
    canActivateChild: [MyTodoGuard],
    children: [
      { path: 'list', component: TodoListComponent, resolve: { todos: MyTodoResolver } },
      { path: 'detail/:id', component: TodoDetailComponent, canDeactivate: [ CanLeaveTodoDetailGuard ] }
    ]
  }
];

然后在 app.routes.ts 中定義一個(gè)路由模塊:

const routes: Routes = [
  { path: '', redirectTo: '/home', pathMatch: 'full' },
  { path: 'home', component: HomeComponent },
  ...TodoRoutes // 這里就是將TodoRoutes列表里的內(nèi)容合并到routes
];
@NgModule({
 imports: [ RouterModule.forRoot(routes) ],
 exports: [ RouterModule ]
})
export classAppRoutingModule{ }

最后,在AppModule里面引入這個(gè)路由模塊。

這種方式實(shí)現(xiàn)的路由無(wú)法實(shí)現(xiàn)子模塊的延時(shí)加載,要實(shí)現(xiàn)延時(shí)加載,首先要將todo模塊的路由修改成子路由模塊,也就是要修改 todo.routes.ts

// 省略import
export const TodoRoutes: Route[] = [
  {
    path: 'todo',
    canActivateChild: [MyTodoGuard],
    children: [
      { path: 'list', component: TodoListComponent, resolve: { todos: MyTodoResolver } },
      { path: 'detail/:id', component: TodoDetailComponent, canDeactivate: [ CanLeaveTodoDetailGuard ] }
    ]
  }
];
// 通過(guò)下面的方式定義了一個(gè)子路由模塊
@NgModule({
 imports: [ RouterModule.forChild(TodoRoutes) ],
 exports: [ RouterModule ]
})
export classTodoRoutingModule{ }

這里,我們定義了一個(gè)子路由模塊, TodoRoutingModule ,它使用 RouterModule.forChild(TodoRoutes) 來(lái)創(chuàng)建。跟整個(gè)App的路由模塊比較的話,主路由模塊使用 RouterModule.forRoot(routes) 來(lái)定義。

定義好了子路由模塊,我們就在子模塊里面引入它既可:

// 省略import
@NgModule({
 imports: [CommonModule, FormsModule, TodoRoutingModule ],
 declarations: [TodoListComponent, TodoDetailComponent, TodoItemComponent],
 providers: [TodoService, MyTodoResolver, MyTodoGuard, CanLeaveTodoDetailGuard]
})
export classTodoModule{}

這樣,我們就定義好了一個(gè)子模塊。當(dāng)用戶(hù)打開(kāi) /todo/list /todo/detail/* 時(shí),這個(gè)子模塊里面的相關(guān)頁(yè)面就會(huì)展示,它也不會(huì)跟其他模塊有任何交互。也就是說(shuō),進(jìn)入和離開(kāi)這個(gè)子模塊,都是通過(guò)路由跳轉(zhuǎn)實(shí)現(xiàn)。這個(gè)子模塊也是完全獨(dú)立的,可以獨(dú)立開(kāi)發(fā),也可以很容易就用到其他應(yīng)用里面。

延時(shí)加載子模塊

下面,我們就可以通過(guò)修改路由的配置,使得todo模塊實(shí)現(xiàn)延時(shí)加載。Angular的路由模塊已經(jīng)提供了 loadChildren 定義可以直接幫我們實(shí)現(xiàn)該功能。下面就是新的app路由定義

const routes: Routes = [
  { path: '', redirectTo: '/home', pathMatch: 'full' },
  { path: 'home', component: HomeComponent },
  { path: 'todo', loadChildren: 'app/todo/todo.module#TodoModule' },
  { path: 'lazy', loadChildren: 'app/lazy/lazy.module#LazyModule' }
];

@NgModule({
 imports: [ RouterModule.forRoot(routes) ],
 exports: [ RouterModule ]
})
export classAppRoutingModule{ }

在這里,我們對(duì)于 todo 路徑,交給 app/todo/todo.module 里面的 TodoModule 模塊處理。而在 TodoModule 模塊里,已經(jīng)有一個(gè)子路由的定義。

最后,再修改 app.module.ts ,保證它里面不再引入 TodoModule 。如此一來(lái),我們?cè)谥髂KAppModule里面,沒(méi)有引入 todo 模塊的任何組件或服務(wù)。這樣就能在完全脫離 TodoModule 模塊的情況下,運(yùn)行主模塊的功能。當(dāng)用戶(hù)打開(kāi) /todo 里面的url時(shí),就加載 app/lazy/lazy.module 里面的 LazyModule 模塊,并交由它來(lái)處理響應(yīng)的url。

總結(jié)一下,實(shí)現(xiàn)延時(shí)加載子模塊,主要是要注意下面幾點(diǎn):

  1. 子模塊的路由用 RouterModule.forChild(TodoRoutes) 方式定義。
  2. 主模塊不要引入子模塊,也不要引入子模塊的任何組件或服務(wù),否則子模塊就會(huì)被打包進(jìn)主模塊里。
  3. 只有子模塊才會(huì)用到的Service在子模塊的 providers 里面定義,如果是主模塊和子模塊都會(huì)用到的Service就用公用模塊的方式定義。要注意這個(gè)Service的實(shí)例只能有一個(gè)。

運(yùn)行

接下來(lái)我們來(lái)看看運(yùn)行的結(jié)果。(注意根據(jù)運(yùn)行環(huán)境不同,文件大小會(huì)不一樣)

不啟用延時(shí)加載

首先,我們?cè)?app.module.ts 引入 TodoModule ,這樣 todo 模塊不是延時(shí)加載的,只有 lazy 模塊是延時(shí)加載的。我們使用 ng serve 的方式運(yùn)行測(cè)試服務(wù)器,并打開(kāi)頁(yè)面,打開(kāi)幾個(gè)頁(yè)面以后,網(wǎng)絡(luò)請(qǐng)求如下:

淺析Angular2子模塊以及異步加載 

從圖中可以看到,有一個(gè)3.4M的main的js文件,下面的 1.chunk.js 的 lazy 模塊延時(shí)加載的。打包的文件確實(shí)是非常的大,因?yàn)閘azy模塊非常簡(jiǎn)單,只是顯示了一個(gè)字符串在模板里。所以它的大小也非常小,才5.8k。

延時(shí)加載模式

下面在把 TodoModule 模塊從 app.module.ts 去掉,這樣, todo 模塊就是延時(shí)加載的,再看一下網(wǎng)絡(luò)請(qǐng)求:

淺析Angular2子模塊以及異步加載 

這下main文件變成了3.1M,lazy模塊對(duì)應(yīng)的js文件是 1.chunk.js ,還是5.8k,todo模塊對(duì)應(yīng)的文件 0.chunk.js 是324K??梢钥匆?jiàn)一個(gè)很簡(jiǎn)單的todo模塊,里面有service, rosolver, guards, 還有3個(gè)組件,里面分別都有模塊、css,雖然文件不少,但是他們的實(shí)現(xiàn)實(shí)際上都很小。只是一個(gè)模塊的文件,在未壓縮的情況下就有300多K,讓我這個(gè)Angular2的忠實(shí)粉絲都無(wú)語(yǔ)。

延時(shí)加載-prod模式

一般我們?cè)诓渴饝?yīng)用的時(shí)候,都會(huì)使用壓縮、混淆、合并等方法來(lái)減少最終文件的大小。使用angular-cli工具,除了在編譯的時(shí)候提供打包的功能,甚至在測(cè)試的時(shí)候,也可以啟用壓縮選項(xiàng)。我們可以運(yùn)行 ng serve -pro 來(lái)使用 prod 模式來(lái)啟動(dòng)測(cè)試服務(wù)器。在啟動(dòng)的過(guò)程中,可以看到很多類(lèi)似下面的日志:

WARNING in 0.005fea95566fdabe23df.chunk.js from UglifyJs
Dropping unused function scheduleMicroTask [/Users/mavlarn/mydev/blog/angular2-tutorial/angular2-routes-lazy-module-webpack/~/@angular/forms/src/facade/lang.js:21,0]

可以看出,angular-cli的 prod 模式下編譯的時(shí)候,去除了很多不需要的代碼,這就是angular的 Tree Shaking 的功能。

運(yùn)行以后,網(wǎng)絡(luò)請(qǐng)求如下:

淺析Angular2子模塊以及異步加載 

這下main文件減少到了221K,lazy模塊對(duì)應(yīng)的js文件是 1.chunk.js ,只有1.0k,todo模塊對(duì)應(yīng)的文件 0.chunk.js 是17.9K??偣泊笮〈蟾攀?40K左右,如果再使用GZip壓縮,應(yīng)該可以到6,70K左右。在官方文檔里提到,一個(gè)Angular2的簡(jiǎn)單實(shí)例,通過(guò)Tree Shaking、壓縮、GZip,最終下載的包大小有50K。我們這個(gè)實(shí)例畢竟稍微復(fù)雜,實(shí)現(xiàn)了大多數(shù)的通用功能,如路由、guard、resolver、表單,也是用到了Rxjs里的 Observable ,所以最終壓縮后能有70K左右的話,也符合官方文檔的說(shuō)法。

編譯后

最后,我們?cè)偈褂?code> ng build --prod 來(lái)看看用prod模式編譯后的大?。?/p>

淺析Angular2子模塊以及異步加載 

結(jié)果出乎意外,main文件的大小比上面在prod模式下運(yùn)行測(cè)試服務(wù)器大很多,達(dá)到800多K。應(yīng)該是編譯過(guò)程需要某些參數(shù),或者是當(dāng)前的angular-cli有什么bug。

再使用 ng build --prod --aot 編譯,main文件的大小是446K。雖然小了一點(diǎn),但是也不符合預(yù)期。

總結(jié)

先說(shuō)延時(shí)加載,應(yīng)該都知道可以減少第一次加載的文件的大小。特別是當(dāng)某個(gè)模塊使用了一些比較大第三方的js庫(kù),例如圖形庫(kù)等,那么,把這些模塊獨(dú)立出來(lái),使用延時(shí)加載的方式,可以大大減少首次加載的時(shí)間。對(duì)于Angular2的應(yīng)用來(lái)說(shuō),如果我們要定義 Component ,就從 @angular/core 里面引入 Component ,需要定義路由就從 @angular/router 里面引入`Router。所以,只要我們?cè)O(shè)計(jì)好了整個(gè)App的模塊、組件、路由,我們就可以利用延時(shí)加載的功能使得首頁(yè)文件盡可能的小。

使用模塊化的開(kāi)發(fā),也能給我們的開(kāi)發(fā)和維護(hù)帶來(lái)很大的便利,項(xiàng)目越大越大,模塊化和組件化帶來(lái)的便利就越明顯。

目前Angular2的天坑

在網(wǎng)上,經(jīng)??梢钥吹揭恍┪恼抡f(shuō)Angular1或者2的一些坑。實(shí)際上,大部分都是因?yàn)槭褂貌划?dāng),或者沒(méi)有按照最佳實(shí)踐去使用,特別是Angular1。雖然Angular1有本質(zhì)上的性能問(wèn)題,但是,通過(guò)良好的整體設(shè)計(jì)、良好的 代碼規(guī)范和質(zhì)量,還是可以開(kāi)發(fā)出很流暢的手機(jī)web應(yīng)用。

但是,在準(zhǔn)備這篇文章中的實(shí)例時(shí),卻遇到了幾個(gè)嚴(yán)重的問(wèn)題,讓我這個(gè)Angular2的忠實(shí)粉絲也很無(wú)奈。

Angular 2.2.2及以上版本的BUG

我在實(shí)例中使用的Angular的版本是2.2.1,如果用的版本是2.2.2 ~ 2.3.0之間,在運(yùn)行或編譯的時(shí)候,可能會(huì)出現(xiàn)如下的錯(cuò)誤:

ngCompiler.ReflectorHost is not a constructor
TypeError: ngCompiler.ReflectorHost is not a constructor

可以上Github查看該 issue 的情況。如果遇到這種問(wèn)題,只能先使用2.2.1的版本。

Angular-Router

在這個(gè)實(shí)例中,延時(shí)加載的todo模塊里面有一個(gè)service,我們使用Angular的依賴(lài)注入的功能自動(dòng)初始化以及諸如這個(gè)服務(wù)的實(shí)例。但是,在3.1.2及以上的版本里面,這個(gè)服務(wù)會(huì)被創(chuàng)建多次,每次激活相關(guān)路由的時(shí)候,就會(huì)創(chuàng)建一次。而且,只有在延時(shí)加載的模式才會(huì)發(fā)生這種錯(cuò)誤。相關(guān) issue

TypeScript

在我之前的教程里,判斷用戶(hù)是否具有某種權(quán)限,使用了如下的方法:

hasRole(role: string): boolean {
  return this.account && this.account.roles.includes(role);
}

但是,更新了TypeScript以后,該方法就不存在了,原因可以查看 這個(gè) .

所以改成了用 indexOf(role) > 0 來(lái)判斷列表里是否存在一個(gè)字符串。

雖然目前Angular還不是十分穩(wěn)定,有一些Bug,甚至TypeScript也不穩(wěn)定,但是,相信這些問(wèn)題都能夠很快解決。而且隨著框架越來(lái)越成熟,也會(huì)越來(lái)越穩(wěn)定。

而且,Angular2+Typescript的開(kāi)發(fā)方式也十分便利,Typescript的強(qiáng)類(lèi)型檢查能夠幫助我們減少編碼的錯(cuò)誤,提高效率。而且,我們也可以很方便的查看框架的API,能省去很多查資料的時(shí)間。

Angular2的很多思想非常適用于開(kāi)發(fā)大型的應(yīng)用。如果開(kāi)發(fā)過(guò)大型的Java項(xiàng)目,就會(huì)發(fā)現(xiàn)學(xué)習(xí)Angular2是一件非常容易的事情。Angular2引入了很多面向?qū)ο蟮目蚣艿乃枷?,而這些,都是在面向?qū)ο箢I(lǐng)域開(kāi)發(fā)大型項(xiàng)目的多年開(kāi)發(fā)經(jīng)驗(yàn)。這些經(jīng)驗(yàn)應(yīng)用到前端開(kāi)發(fā),也能幫助我們更方便的開(kāi)發(fā)和維護(hù)大型的前端項(xiàng)目。

雖然,Angular2的應(yīng)用最終的打包文件非常大(我們這個(gè)實(shí)例即使壓縮完后也有70K左右,但是如果用VUE的話會(huì)比這個(gè)小很多),但是隨著Angular2的越來(lái)越穩(wěn)定,各種開(kāi)發(fā)工具越來(lái)越成熟,相信文件大小的問(wèn)題也能夠有一個(gè)比較好的解決方案。因?yàn)锳ngular2的AOT、Tree Shaking的特性,為解決大小的問(wèn)題提供了前提。

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持億速云。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI