Angular 控制流与延迟视图揭秘
2023 年 11 月 8 日 Angular 团队发布了 Angular 17 开发预览版,在新的版本,Angular 添加了许多激动人心的特性,其中就包含新的控制流和延迟视图
新版 Angular 增加一个 Block
的概念, Block
是模板中的一种新语法结构,控制流和延迟视图也是基于这种语法结构来实现的。
控制流
什么是控制流?
控制流是指程序中决定语句执行顺序的机制。它通过顺序、选择(条件判断)、循环等结构,使程序能够根据不同条件或规则执行不同的代码块,实现灵活的逻辑控制,代码中常见的 if-else
for
都属于控制流语法。
Angular 中的控制流
Angular 16 及之前的版本中的控制流是基于微语法和结构指令来实现的,比如:
问题
不够直观简洁(目前模板中基于指令的控制流会使模板代码变的冗长,这种冗长会导致降低代码的可读性)
灵活度差(微语法模型也不支持控制流语句的多个关联子模板,所以 Angular 无法解决
*ngIf
的情况else
使用起来非常尴尬的问题)类型检查不友好
独立组件需要引入
新的控制流
关注 Angular 的同学了解,Angular 一直在推动 Zoneless 和 Singals 的工作,Angular 目前的控制流指令是无法在 Zoneless 的应用中运行的,在考虑修改现有指令以支持 Zoneless 应用时,Angular 团队决定不这样做,因为潜在的重大更改(需要兼容旧的应用程序)和代码复杂性增加。相反,他们借此机会选择引入一种新的内置控制流语法,该语法既支持 Zoneless 应用程序,又解决微语法长期存在的 DX(开发体验)问题。
语法
官方在 RFC 时候提出了 #-syntax
类似 HTML 的标签语法,如 {#if}
、 {:else}
和 {/if}
不过在 RFC 讨论阶段,大部分人更喜欢 @-syntax
的语法,最终 Angular 团队通过调查和研究发现,大部分人也都认可这个方案,最终确定了使用了 @-syntax
的语法
使用
IF-ELSE
新的条件控制使用 @if
和 @else
块来定义,与旧的 *ngIf
语法相比,新的控制流同时还支持了 ”else if“ 的能力
新的语法依旧支持通过 async
订阅
Switch
新的 Switch 与 @switch
@case
@default
三个块组成,支持直接在块中传入内容,新的这种使用方式使我们的代码更清晰直观
For Loop
新的 for 循环我认为变化最大的,在语法上,新的 @for
简化了 *ngFor
的使用,不需要写 let
,同时直接直接指定 track
属性,在性能上,Angular 也做了比较大的优化,同时还内置支持了 empty 场景。
新的语法依旧支持通过 async
订阅
内置的隐藏变量依旧与 *ngFor 保持一致
延迟视图
在 Angular 16 中如何实现延迟加载组件?
@defer
Angular 支持通过 Router 延迟加载应用程序的某些部分(延迟路由),单个组件的延迟加载可以通过 dynamic import()
和 ngComponentOutlet
实现,但这种方法可能很复杂且容易出错,因此 Angular 17 在核心框架中引入一种更符合人体工程学的延迟加载组件的方法 @defer
, @defer
不仅仅支持延迟组件,同时也支持延迟加载指令和管道,随着新的 @defer
的引入,我们可以更细颗粒度的控制我们的加载资源,优化应用初始包的体积,提升用户加载的速度。
使用限制
@defer
块中的组件必须是独立组件,非独立组件不支持延迟加载并且会立即加载,即使它被包裹在@defer
块中不能在
@defer
块以外引用这个组件,包括使用@ViewChild
查询这个组件
使用
@defer
@defer
块中的内容最初不会显示,当满足指定的触发器或条件并获取依赖项,块中的内容才会展示,默认情况下,当浏览器状态变为空闲状态(Idle)时,会触发 @defer
加载。
@placeholder
默认情况下, @defer
块在触发之前不会呈现任何内容,我们可以定义 @placeholder
可选的块,声明在触发延迟块之前要显示的内容,当延迟内容加载完成后, @placeholder
块中的内容会销毁,需要注意的是在 @placeholder
块中的内容永远都是立即记载的
@placeholder
块接受一个可选参数 minimum
来指定应显示此占位符的时间,单位支持 s 和 ms。 minimum
是为了防止延迟项加载过快导致内容闪烁。
@loading
@loading
与 @placeholder
类似,也是是一个可选块, @loading
和 @placeholder
区别是 @loading
块中指定的内容只有当资源加载时候才会展示,在资源未被加载之前永远展示的都是 @placeholder
,` @loading
块支持接收两个参数 after
和 minimum
@error
@error
比较好理解,我们可以在 @error
中指定依赖加载失败时候展示的内容,与 @placeholder
和 @loading
, @error
块的内容也是立即加载的,并且也是可选的
触发器 Triggers
默认情况下,当浏览器状态变为空闲状态(Idle)时,会触发 @defer
加载,不过我们也可以根据自己的需求指定其他的触发器,Angular 提供了两种触发方式 When
和 On
When 条件触发
指定一个条件,当满足这个条件时触发
On 支持以下几种内置的触发器
on idle (浏览器闲时触发,利用浏览器的 requestIdleCallback
特性)
on immediate(应用渲染完后立即触发)
on timer(指定一个时间间隔后触发)
on viewport(Placeholder 或指定元素进入可视区域后触发,利用的 IntersectionObserver
特性,组员保证 Placeholder 必须是一个 DOM 节点)
on hover(Hover Placeholder 或者 Hover 指定某个区域触发)
on interaction(Placeholder 或者指定元素触发 click 或者 keydown 事件时触发)
On 和 When 可以组合使用
预获取资源 Prefetch
@defer
允许指定何时触发依赖项的预取条件,我们可以使用 prefetch
关键字, prefetch
可以 when
和/或 on
结合声明触发器
一些注意事项
@defer
块中如果不包含任何依赖项(组件、指令、管道)等,则这个@defer
块是无效的@defer
块与ng-content
结合使用时,外部投影的内容不会延迟加载,如果要延迟加载投影的内容,可以在外部将投影的内容单独包装到@defer
块中@defer
支持嵌套,我可能指定进入可视区域时渲染一个大组件,然后用户 Hover 某个元素时渲染一个内部子组件,不过需要注意尽量避免这样的使用,可能会导致性能问题
编译结果
编译后, @defer
块中的组件会打包成一个一个 chunk 文件用于单独的加载
测试
Angular 提供 TestBed API 来简化测试块和在测试过程中触发不同状态的过程 @defer
。默认情况下, @defer
测试中的块是“暂停”的,我们可以手动在状态之间转换
插件支持
Prettier
QA
Q:控制流会支持自动迁移吗?
A:Angular 将会提供 Schematics 工具自动迁移
Q:新的控制流会不会影响 ViewChild 查询结果?
A:不会,与原有的结构指令行为一致
Q:现有结构指令会废弃吗?
A:不会,结构指令是 Angular 中应用程序架构的一个基本功能,Angular 没有计划删除它们
Q:未来支持自定义 Block 吗?
A:暂时不会,未来可能会
Q:在 @defer
可以定义自己的 on 的触发器吗?
A:不可以,可以用 When 来实现自定义触发器
Q:CDK 的虚拟滚动会收到影响吗?
A:CDK 目前将继续为虚拟滚动和其他用例提供其现有的结构指令。Angular 将研究将 CDK 的一些结构指令转换为内置语法,或者将扩展点添加到现有语法中供 CDK 构建(例如,支持虚拟 for
滚动)
评论