You are on page 1of 23

给 J a v a e r 的 A n g u l a r 教 程

模块与路由
模块概述
2
什么是模块?

比类更大的封装单位
• 包含多个组件、指令、管道等
• 有些是私有的,有些是公开的
• 模块之间可以定义依赖关系

类似于 Java 中的 package


• 有些类是 package 内可见的
• 有些类是对外公开的

3
模块有什么用?

勾勒领域的边界(业务架构)
• 应用通常可以划分成很多个业务领域
• 这些业务领域往往是经过多年的发展沉淀下来的,
具有本质上的内聚性
• 除非业务本身发生剧变,否则在业务的发展演化中,
领域划分通常是最稳定的

勾勒团队的边界(组织架构)
• 模块可以交给一个独立团队负责开发维护
• 模块本身的封装性,可以避免模块之间相互干扰

4
合理拆分模块的原则

使用 DDD 建模
• 理解业务本身的边界
• 定义模块及其联系(业务架构)

调整团队架构
• 让团队架构对齐到业务架构

康威定律
• 业务、团队、代码的架构要互相对齐

5
模块还可以支持惰性加载

前端的惰性加载是提高性能的关键
• 再怎么优化,应用的本质性复杂度是不会降低的,
因此总的代码量不会减小,只能按需加载

模块是惰性加载的天然边界
• 模块在物理层还可以看做 jar 文件
• 文件级的惰性加载会增加编程复杂度,如果内聚性
不够则需要付出额外的努力来维护完整性
• 模块级的惰性加载则是一个恰到好处的粒度,不用
担心完整性

6
可以用路由实现零成本的惰性加载

按需加载路由的定义 为加载路由指定 path


{ • 所有以这个 path 开头的 url
path: 'auth', 都会先检查这个模块是否已
loadChildren: () =>
import('./auth/auth.module') 加载,如果没有则加载它
.then(m => m.AuthModule),
}, • 它的子路径会分发给该模块

使用 import 关键字加载
• 这是 es6 提供的特性
• Angular CLI 打包时自动生成
惰性加载的 js 文件
7
路由概述
8
什么是路由?

一种对请求进行分发的机制
• 硬件:根据 ip 地址找到对应的设备
• Spring:根据请求找到对应的 Controller
• Angular:根据请求找到对应的 Component

几个概念
• 路由机制:路由的整个技术体系
• 路由定义:对路由进行配置的一组信息
• 路由器:Router 服务,负责分发请求
• 当前路由/激活路由:正在显示的组件所关联到的路由

9
路由定义语法提要

path
• :id 表示路径变量
• 可以为空,有妙用
const appRoutes: Routes = [ • 可以用 ** 通配符,定义 fallback 路由
{ path: 'crisis-center', component: CrisisListComponent },
{ path: 'hero/:id', component: HeroDetailComponent }, data
{ path: 'heroes', component: HeroListComponent, data: • 附加数据,可以从路由信息中取到
{ title: 'Heroes List' } },
{ path: '', redirectTo: '/heroes', pathMatch: 'full’ },
{ path: '**', component: PageNotFoundComponent } redirectTo
]; • 路由重定向

pathMatch
• ‘full’|’prefix’(默认)
• 匹配方式:前缀匹配或完全匹配
10
几种特殊的路由

空路径路由 path: ''


• 可用来定义默认路由
• 可用来定义子模块的 Layout 组件

无组件路由
• 插入一段路径,提高 URI 的表意性
• 对子路由进行合理的分组

** 路由
• 无法匹配任何一个路由时,就 fallback 到这里
• 可以用来实现 404 页面

11
使用路由进行布局(Layout)

多级布局怎么处理?
• 划分出各级布局组件,把它们关联到空路径路由上

布局的实现过程
• 向 BA/UX 了解业务架构和交互架构
• 设计合理的 URL 体系
• 不同层次的布局风格出现变化时,为其设计布局组件
• 布局组件要尽可能精简,拆成子组件
• 在不同层级的布局组件上挂不同的模型(服务)

布局风格差异太大时,考虑拆分成多个应用

12
路由传参

传入参数
• <a [routerLink]=“[‘seg1’, {a: 1}, ‘seg2’, {b: ‘2’}]”>链接</a>
• 最终生成的 url:”seg1;a=1/seg2;b=2”
• 这种称为 Matrix 参数,可以为每一个 URL 段分别指定参数
• TBL 的提案,没进标准,但是主流 Web 服务器支持

取出参数
• 注入 private route: ActivatedRoute
• 直接取参:this.route.snapshot.paramMap.get('b')
• 订阅参数流:this.route.paramMap.subscribe(params=>
{ params.get(‘b’);… })
• 串联管道:
this.route.paramMap.pipe(map(params=>params.get(‘b’)).subscribe(…)

13
组件实例复用

路由为什么要复用组件实例?
• 性能保障:避免不必要的 DOM 增减
• 体验保障:避免页面闪烁

什么情况下会复用?
• 简单来说:如果前一个路由和后一个路由都使
用同一个路由组件,这个路由组件就会被复用
• 比如 Master/Detail 结构的页面,当在 Master
中点链接切换 Detail 时,Detail 就会被复用

14
参数流的订阅是什么鬼?

组件实例复用带来的问题
• ngOnInit 只会被调用一次,复用时不调用
• 这意味着,如果你在 ngOnInit 中取 snapshot,那么当参数
变化时,你就没有机会去更新

用订阅解决这个问题
• 我们之所以关心 ngOnInit,其实是需要让视图模型(组件类)
随着路由参数而变化
• 所以,我们可以用更直白的方式:订阅路由参数
• 用这种方式,我们可以对视图模型进行 patch,避免重复
• 除订阅外,我们还可以用 RxJS 的管道来处理它

15
路由守卫与路由事件
16
路由守卫

路由守卫就是站在路口的卫兵
• 他可以验证身份
• 他可以记录访客信息
• 他可以让你帮着送信
• 他可以在你离开前提醒你遗漏了物品

路由守卫可以实现多种 AOP 逻辑
• 登录检查 CanActivate
• 记录日志/支持访问统计代码 CanActivateChild
• 在路由中(异步)获取额外信息并传给组件 Resolve
• 检查是否有未保存的编辑 CanDeativate

17
路由事件

Router.events 流
• 它是路由守卫的底层技术
• 它比路由守卫更详细、更强大
• 但是你得自己去写判断语句,不利于拆分代码

可用于调试
• 当路由方面出现奇怪的行为时,你可以在这里打一
些日志来帮助调试

可用于支持统计分析

18
技巧与坑
19
最佳实践

让每个界面都有自己的路由
•方便调试
•弹出界面可以使用多重路由来支持

拆分出独立的路由组件
•适用于路由多变的情况
•分散在很多地方的路由解析代码不易维护
•路由组件只负责跟路由参数打交道
•根据路由参数更新模型(服务)或传给其子组件

优先使用相对路径
•相对路径可以让路由局部化,有助于单独修改模块和 URL

仿照 REST API 的 URI 规则进行设计


•但可能要在最后一段增加一个动作标识,如 /users/1/edit

20
技巧与坑(一)

你可以给路由器配置参数
• RouterModule.forRoot(routes, {...config})
• useHash: 使用 # 模式代替 H5 路径
• preloadingStrategy: 对某些模块进行预加载以提高加载速度
• onSameUrlNavigation: 可用来实现就地刷新逻辑
• paramsInheritanceStrategy: 参数是否自动继承。自动继承
时,各级路由的参数要注意避免重名

可以用 Query 参数传递全局参数


• 它会在不同路由之间自动传递
• 比如 locale 信息

21
技巧与坑(二)

多级 redirectTo
• redirectTo 的值为相对路径时,可以多级跳转,表示相对于当前
路径进行跳转
• 如果值为绝对路径则不能多级跳转

参数继承
• 路由参数默认是不自动继承的,除非空路径路由或无组件路由
• 不自动继承时,访问上级路由的参数需要使用 parent 找到上级
• 如果需要继承,就得给路由器配置 paramsInheritanceStrategy:
‘always’
• 你很可能不需要 ‘always’,在不同层次上创建不同的模型,让它
们彼此通讯

22
谢谢

You might also like