发表于: 2021-11-20 23:51:19
0 889
angular中的懒加载和预加载
懒加载的场景
应用在启动时,有些模块可能根本就用不上,比如在一个商场系统中,用户打开首页时,只需要向用户展示商品,支付模块此时根本没用,因此对于支付模块就可以使用懒加载。
- 优点:使用懒加载后,只加载必须的模块,因此首次加载的包体积大大减小,这样就可以加快应用的启动速度。
- 缺点:或者说懒加载还是不能解决的是,如果某一个懒加载的模块体积过大,当加载时还是可能发生加载速度慢的情况,用户的页面被阻塞,体验下降。
可见,懒加载解决了请求额外资源导致的性能消耗,加载速度过慢的问题。
预加载的场景
当应用启动后,后续功能可能会被使用到,因此在用户可以使用基础的功能后,再去加载可能使用到的模块,比如前述中的支付模块。这样一旦用户需要对某个商品付费时,支付模块可以立即使用而无需等待。
通过预加载,可以弥补懒加载中的缺点,基本功能可用后在后台加载那些体积较大,加载耗时较长的模块。
代码实现
以商场系统为例,假设我们有以下几个模块
- AppModule: 应用的根模块,angular脚手出来的项目都有这么一个模块,作为整个项目的根模块,启动应用,进行全局配置等。
- HomeModule: 应用主页模块,应用启动后就需要展示的模块。
- BookModule: 应用内的一个和书籍相关的模块,只有当用户查看此部分内容时才加载,需要懒加载。
- AuthModule: 用户登录,注册等功能所处的模块,需要预加载。
- PayModule: 应用的支付模块,所有和支付相关的功能都在此模块内,体积较大,需要预加载。
AppModule
在AppModule中导入我们定义好的AppRoutingModule。
import { HomeComponent } from './home/home.component';
@NgModule({
...
imports: [
...
HomeModule,
AppRoutingModule,
...
]
...
})
export class AppModule { }
AppRoutingModule里定义项目的根路由。
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
const appRoutes: Routes = [
{ path: 'home', component: HomeComponent },
{ path: 'book', loadChildren: './book/book.module#BookModule' },
{ path: 'pay', loadChildren: './pay/pay.module#PayModule' },
{ path: 'auth', loadChildren: './auth/auth.module#AuthModule' },
{ path: '', redirectTo: '/home', pathMatch: 'full' },
];
@NgModule({
imports: [
RouterModule.forRoot(appRoutes),
],
exports: [
RouterModule,
],
})
export class AppRoutingModule { }
在AppModule中直接导入了HomeModule,因此HomeModule在应用启动时就会加载进来。 在AppRoutingModule中我们配置了路由规则,home没什么说的,直接把用户导向到主页,对应的HomeComponent在HomeModule中被声明和导出。 book,pay,auth这三个模块都使用了懒加载,写法就像上面那样,和直接加载的模块最大的区别是不声明具体的组件,而是使用loadChildren来指导路由加载所需的模块,在其它模块内再次配置模块的子路由。 最后一个空路由,重定向路由,没有提供路径时定向到主页。
添加预加载策略
目前为止这些代码和预加载还没有发生任何关系,我们来加上:
import { PreloadAllModules } from '@angular/routes';
RouterModule.forRoot(
appRoutes,
{preloadingStrategy: PreloadAllModules}
)
在这里,我们告诉angular,预加载使用的策略是:预加载所有的懒加载模块。显然,这不是我们想要的,既然 angular 需要一个预加载的策略,我们来看一下这个策略。
export declare class PreloadAllModules implements PreloadingStrategy {
preload(route: Route, fn: () => Observable<any>): Observable<any>;
}
从这个声明文件上看,这个策略实现了 PreloadingStrategy 接口,声明文件中很容易找到,就在这个声明的上面:
export declare abstract class PreloadingStrategy {
abstract preload(route: Route, fn: () => Observable<any>): Observable<any>;
}
Ok,这下明白了,要改写预加载策略,我们只需要提供这么一个类,同时实现 PreloadingStrategy 接口(这里把类作为接口使用了)。
自定义预加载策略
首先在路由设置上加个属性
...{ path: 'pay', loadChildren: './pay/pay.module#PayModule', data: { preload: true} },{ path: 'auth', loadChildren: './auth/auth.module#AuthModule', data: { preload: true} },...
然后实现自定义的预加载策略
@Injectable()export class SelectivePreloadingStrategyService implements PreloadingStrategy {
constructor() { }
preload(route: Route, load: () => Observable<any>): Observable<any> {
if (route.data && route.data['preload']) {
return load();
} else {
return of(null);
}
}}
在AppModule的providers中加入这个策略
...
providers: [SelectivePreloadingStrategyService],
...
需要预加载的模块,我们返回了load方法调用的结果,不需要预加载时简单的返回一个发出null的Observable。如果深究这里的load方法和route是怎么回事,可以看一下angular的源码部分。源码位置:packages/router/src/router_preloader.ts
修改RoutingModule上的预加载策略:
import { SelectivePreloadingStrategyService } from './providers/selective.preloading.strategy.ts';
RouterModule.forRoot(
appRoutes,
{preloadingStrategy: SelectivePreloadingStrategyService}
)
现在,只有auth和pay两个模块可以被预加载进来,而book模块只有在用户访问这个模块的相关页面时才加载。
当然还有个问题,pay模块体积较大,我们还想推迟它的加载时间,因为实际情况可能是只有当用户登录以后才可以使用支付功能,那么这个时候我们就要预判用户是否有登录的可能性,假定用户导航到了登录页面后我们认为有可能登录,此时我们只要改造加载策略,修改配置就可以了。
此时不能把预加载的条件写死在配置中:
...
{ path: 'pay', loadChildren: './pay/pay.module#PayModule' }, // 删除data
...
在策略的preload函数中添加一些额外的条件:
preload(route: Route, load: () => Observable<any>): Observable<any> {
if (route.data && route.data['preload'] || location.href.includes.('auth') ) { // 如果当前的href中有auth时,再加载payModule。
return load();
} else {
return of(null);
}
}
当然新加入的条件可能不够严谨,你当然可以设置另外的条件,或者通过注入其它服务来提供需要的条件,只要记得需要预加载时返回 load(),否则返回of(null)就可以了。
评论