发表于: 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)就可以了。



返回列表 返回列表
评论

    分享到