这里是我在几天内实现 @defer 进行细粒度代码拆分的完整步骤 — 步骤详解
Source: Dev.to
@defer 基础与设置
Angular 的 @defer 块是性能提升的关键,它可以把重量级模板区域(如图表或仪表盘)拆分成独立的 JavaScript 包,仅在视口进入、用户交互或浏览器空闲等触发事件时才加载。这样可以大幅削减首屏负载,加快最大内容绘制(LCP)以及整体应用启动速度,尤其在大型单页应用中,用户不需要立刻使用的功能可以后置加载。
成功的前置条件
- Angular v17 或更高(从 Angular 18 起正式支持可延迟视图)。
@defer块内部的组件、指令和管道必须是 standalone,且不能在同一文件的其他位置被引用(例如不能有ViewChild查询或会导致提前加载的外部导入)。- 传递依赖可以是 standalone 或基于 NgModule 的混合,但占位内容应保持轻量,因为它们的依赖会在首次渲染时一起加载。
基本语法示例
@defer (on viewport) {
}
@placeholder {
}
- 触发方式:
idle(默认)、interaction、hover、timer(2s),或自定义when showLargeChart。 - 加载指示器:
@loading (minimum 1s) { } - 错误处理:
@error { Retry? } - 预取:
prefetch on idle可在不渲染的情况下提前加载代码包。
验证构建成果
- 运行
ng build,检查是否出现类似defer-heavy-chart.js的新块。 - 启动应用,打开 Chrome DevTools → Network,确认只有在触发时才加载对应的重量级包。
- 单元测试中,可通过以下方式模拟 defer 状态:
TestBed.deferBlockBehavior = DeferBlockBehavior.Manual;
实战示例:仪表盘魔法
@defer (on viewport; prefetch on idle) {
}
@placeholder (minimum 500ms) {
}
将数据量大的仪表盘部件以这种方式包装,可在复杂页面上让主包体积减少 30 %+,为折叠区的指标提供流畅体验。
核心触发器与子块
| 触发器 | 描述 |
|---|---|
idle | 通过 requestIdleCallback 在浏览器空闲时触发(默认)。 |
viewport | 当元素进入视口时加载(Intersection Observer)。 |
interaction | 在点击、键盘按下或其他用户交互时触发。 |
hover | 在鼠标悬停或获得焦点时触发。 |
immediate | 在非延迟内容渲染完毕后立即加载。 |
timer(2s) | 在指定延迟后加载。 |
多个触发器可用分号组合(OR 逻辑)。预取单独声明,例如:
@defer (on viewport; prefetch on idle) { ... }
子块实现无缝用户体验
@placeholder– 预先加载的轻量内容,在延迟包加载前展示。使用minimum 500ms可避免闪烁。@loading– 触发后、代码包解析前显示的加载指示。可通过after 100ms; minimum 1s控制时机。@error– 加载失败时的回退 UI。将这些块放在带有aria-live="polite"的容器中,以便屏幕阅读器朗读。
@defer (on hover) {
}
@placeholder { }
@loading { }
@error { }
使用 when 的自定义触发器
@defer (when isVisible()) {
}
when 只会评估一次信号或表达式,评估后不会回退。也可以与预取结合使用:
@defer (when userClicked(); prefetch when dataReady) { ... }
高级模式与优化
嵌套 @defer 与级联避免
嵌套 defer 块可以实现分层懒加载,但不当的嵌套会导致多个代码包同时请求。应错开触发时机以防止请求级联:
@defer (on viewport) {
@defer (on interaction) {
} @placeholder {
Click to expand
}
} @placeholder {
Scroll to see more
}
@if (isExpanded) {
}
- 外层使用
viewport,仅在滚动到该区块时加载。 - 内层等待明确的交互,避免一次性发起大量网络请求。
SSR/SSG 与水合(Hydration)魔法
- 在服务器端,仅渲染
@placeholder内容(或不渲染任何内容),触发器被忽略,因为没有视口或空闲状态。 - 客户端水合时,配置好的触发器会生效,使 UI 可交互。
- 通过
hydrate on触发器开启增量水合,实现 SEO 友好的服务器渲染,同时不膨胀客户端包体。配合prefetch on idle可在水合前预加载代码。
包体分析与 HMR 陷阱
- 安装分析工具:
npm i -D source-map-explorer。 - 使用 source map 构建:
ng build --source-map。 - 运行分析:
npx source-map-explorer dist/**/main.js
交互式树图会展示随着重量级 standalone 组件被拆分,主包体积的缩减情况。
HMR 注意:开发服务器会提前加载所有 @defer 块,绕过触发器。为获得准确测试,请关闭 HMR:
ng serve --no-hmr
可视化示例

ARIA Live 区域的可访问性
屏幕阅读器可能错过占位内容到加载后内容的切换。将 defer 块包装在带有 ARIA live 属性的容器中:
@defer (on hover) { }
@placeholder { }
@loading { }
@error { }
这样可以确保动态内容变化被自动朗读,使 UI 在保持高性能的同时也具备包容性。