Angular 懒加载(Lazy Loading)实战指南:按需加载模块与组件,优化首屏性能

原创2026-10-03 18:01:081,945 阅读
文章标签:文档教程知识库

Angular 懒加载(Lazy Loading)实战指南:按需加载模块与组件,优化首屏性能

本文围绕 Angular 路由懒加载技术展开,讲解如何通过将应用拆分为多个 bundle、在路由激活时异步加载 JavaScript 组件,从而显著降低首屏加载时间。读完本文,你将掌握基于 NgModule 的 loadChildren 懒加载、基于 Standalone 组件的 loadComponent 懒加载、预加载策略配置,以及懒加载与 Angular 特性模块、可延迟视图(@defer)、AOT 编译之间的配合关系,可直接在项目中落地实施。

什么是懒加载:为什么 Angular 应用需要它

懒加载(Lazy Loading)是 Angular 中一项核心性能优化技术:它允许你在特定路由被激活时,才异步加载对应的 JavaScript 组件。默认情况下,Angular 应用的所有模块都是急切加载(Eager Loading)的——应用一启动,所有 NgModule 都会立即下载并执行,无论当前用户是否真的需要它们。

对于路由数量多的大型应用,急切加载会带来两个明显问题:

  1. 首屏 bundle 过大:所有功能代码被打包进少数几个初始文件,用户首次访问就要下载大量用不到的代码,加载时间被拉长;
  2. 带宽浪费:用户可能只访问其中一两个页面,却被迫下载了整个应用。

懒加载正是解决这两个问题的设计模式:它将应用拆分成多个 bundle,按需加载——用户导航到哪个路由,才下载哪个路由对应的代码块。从仓库中 Angular 学习路径的配套文档 Lazy Loading Modules 可以看到同样的定义:懒加载能让初始 bundle 更小,从而帮助缩短加载时间。

一句话概括其核心价值:减少初始加载量,把“所有代码一次下载”变成“用到的才下载”,以更快的首屏换取更好的用户体验。

懒加载的两种主流实现方式

在 Angular 中实现路由级懒加载,取决于你的应用是基于 NgModule 架构还是 Standalone 组件架构。两种方式都在路由配置层面声明,Angular Router 会在导航发生时按需拉取对应代码。

方式一:NgModule 懒加载(loadChildren)

传统 NgModule 架构中,路由通过 loadChildren 指定要懒加载的特性模块(Feature Module)。这也是 Module Architecture 文档强调的典型场景——特性模块“often lazily loaded”(通常被懒加载)。

典型的配置如下:

// app-routing.module.ts
const routes: Routes = [
  {
    path: 'customers',
    // 关键:loadChildren 接收一个工厂函数,返回被加载的模块
    loadChildren: () =>
      import('./customers/customers.module').then((m) => m.CustomersModule),
  },
  {
    path: 'orders',
    loadChildren: () =>
      import('./orders/orders.module').then((m) => m.OrdersModule),
  },
];

这里的 import() 是标准的 ECMAScript 动态导入语法,配合打包工具(如 webpack、Vite)会把 customers.module 及其依赖单独拆成一个 chunk。只有用户导航到 /customers 时,这个 chunk 才会被网络请求并执行。

对应地,CustomersModule 内部需要为自身声明路由:

// customers/customers.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { CustomersComponent } from './customers.component';

const routes: Routes = [{ path: '', component: CustomersComponent }];

@NgModule({
  declarations: [CustomersComponent],
  imports: [RouterModule.forChild(routes)],
})
export class CustomersModule {}

注意其中的 RouterModule.forChild(routes):懒加载模块必须使用 forChild 注册自己的子路由,最终由根模块的 RouterModule.forRoot(routes) 统一调度。这与 Feature Modules 文档中“用特性模块把特定功能代码与其他代码隔离”的组织理念一致——懒加载是特性模块在加载时机上的进一步增强。

方式二:Standalone 组件懒加载(loadComponent)

Angular 15 之后,应用可以全面转向 Standalone 组件架构(组件元数据中设置 standalone: true,直接导入模板中使用的组件、指令和管道,参见 Standalone 文档)。在 Standalone 架构下,不再需要为每个路由准备一个 NgModule,可以直接在路由上懒加载组件本身:

// app.routes.ts
export const routes: Routes = [
  {
    path: 'customers',
    // 直接懒加载一个 Standalone 组件
    loadComponent: () =>
      import('./customers/customers.component').then(
        (m) => m.CustomersComponent
      ),
  },
];

此时 CustomersComponent 只需是一个普通的 Standalone 组件即可:

// customers/customers.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-customers',
  standalone: true, // Standalone 组件无需 NgModule 包裹
  template: `<h2>Customers</h2>`,
})
export class CustomersComponent {}

loadComponent 是比 loadChildren 更轻量的懒加载单元:它去掉了“为懒加载而额外创建 NgModule”的样板代码,直接以组件为粒度拆分 bundle。对于新项目,这是官方推荐的主流写法。

预加载策略:在懒加载与性能之间取平衡

懒加载把下载推迟到导航发生时,但用户点击进入新页面时仍会有一段网络等待。Angular Router 提供了预加载策略(PreloadStrategy),让应用可以在空闲时提前拉取懒加载 chunk,从而兼顾首屏体积与导航体验。

在 RouterModule.forRoot() 中配置:

import { PreloadAllModules } from '@angular/router';

RouterModule.forRoot(routes, {
  // 构建时全量预加载所有懒加载模块
  preloadingStrategy: PreloadAllModules,
})
  • 默认(不配置):完全不预加载,严格按需下载;
  • PreloadAllModules:全部预加载,应用空闲后后台拉取所有懒加载 chunk;
  • 自定义策略:实现 PreloadStrategy 接口,按业务优先级选择性地预加载(例如只预加载高频页面,跳过大型低频页面)。

选择哪种策略取决于应用场景:首屏体积优先选默认不预加载;追求页面切换“秒开”体验且网络条件较好时,可用 PreloadAllModules;两者兼顾则用自定义策略做精细化控制。

懒加载在整个 Angular 性能体系中的位置

懒加载并不是孤立的技巧,它是 Angular 性能优化体系中的一环。仓库的 Performance 文档将其与另外几种关键技术并列:

技术 作用层面 目标
懒加载(Lazy Loading) 路由 / 模块 / 组件粒度 拆分 bundle,按需下载,优化首屏
可延迟视图(Deferrable Views,@defer) 模板内部依赖粒度 推迟模板中组件/指令/管道及其 CSS 的加载
图片优化(Image Optimization) 静态资源 降低图片下载开销
减少 Zone 污染(Zone Pollution) 变更检测 精简变更检测触发频率
慢计算优化(Slow Computations) 运行时 提升运行时效率
水合(Hydration) SSR 应用 让服务端渲染应用更快可交互

两者可以组合使用:路由层用懒加载拆分页面 bundle,页面模板内部再用 @defer 块延迟加载非关键依赖——@defer 以声明式的方式把模板中一段区域包起来,并指定加载条件(参见 Deferrable Views 文档):

<!-- 只有在可见时才加载该区域依赖的组件、指令、管道及其 CSS -->
@defer (on viewport) {
  <app-heavy-chart />
} @placeholder {
  <div>图表加载中…</div>
}

懒加载与构建:AOT 编译与打包

懒加载之所以能生效,依赖于构建工具在编译打包阶段就把动态 import() 的模块拆成独立 chunk。Angular 应用在浏览器运行前必须先经过编译——AoT Compilation 文档指出,AOT 编译器会在构建阶段把 Angular 的 HTML 和 TypeScript 代码转换为高效 JavaScript。AOT 编译与懒加载是天然搭档:

  • AOT 在构建期完成模板与组件的编译,产出更小的产物、更快的浏览器渲染;
  • 懒加载依赖构建期对动态导入的静态分析,打包器据此自动生成命名清晰的 chunk 文件;
  • 构建完成后,你可以在产物目录中看到类似 customers-*.js、orders-*.js 的独立文件,这就是懒加载拆分出的 bundle,只有导航到对应路由时才会被浏览器请求。

从源码结构看,Angular CLI 构建体系(ng build)会为每个懒加载路由生成对应的 chunk,并在路由配置中写入映射关系;运行时 Router 依据该映射按需请求 chunk、实例化模块/组件后渲染到 Router Outlet 标记的出口区域。

实践建议与注意事项

结合上述原理,落地懒加载时有几点值得注意:

  1. 以路由为拆分边界:每个懒加载单元对应一个相对独立的业务页面或功能域,避免把一个页面拆成过多碎 chunk(增加请求数量反而拖慢性能);
  2. Standalone 优先:新代码推荐 loadComponent + Standalone 组件,省去 NgModule 样板,也让拆分粒度更细;
  3. 搭配预加载策略:对用户最可能访问的页面使用自定义 PreloadStrategy 预加载,降低导航等待;
  4. 路由守卫照常生效:懒加载不影响 Guards 的认证/授权拦截逻辑——导航发生时先执行守卫,再按需加载目标代码,可避免未授权用户触发无谓下载;
  5. 模板内小依赖用 @defer:路由级懒加载负责“页面级”拆分,页面内部的次要组件交给 @defer 处理,两级配合效果最佳。

小结

懒加载是 Angular 应用性能优化中投入产出比极高的技术:通过 loadChildren(NgModule 架构)或 loadComponent(Standalone 架构)把应用拆分为多个 bundle,让 JavaScript 组件在对应路由激活时才被异步加载,从而显著缩小首屏包体积、加快应用初始加载速度。结合预加载策略、@defer 可延迟视图与 AOT 编译,你可以在不牺牲用户体验的前提下,把大型应用的首屏性能做到最优。

登录后查看全文
developer-roadmap