复用标签在中台系统非常常见,本质是解决不同路由页切换时组件数据不丢失问题。
最新打开的永远是当前页,而路由复用是指当我们离开当前页时若符合复用条件(即:匹配模式)则保存当前路由所有组件状态(包括子组件),待下一次进入相同路由(即:匹配模式)时再从中缓存中获取到。
如何使用
- 在
app.config.ts 下提供 provideReuseTabConfig() 配置 - 在需要展示标签的地方调用
<reuse-tab>,一般在 src/app/layout/basic/basic.ts,例如:
- <router-outlet />
+ <reuse-tab #reuseTab />
+ <router-outlet (activate)="reuseTab.activate($event)" (attach)="reuseTab.activate($event)" />
注意:若不指定 (activate) 事件,无法刷新未缓存过的当前标签页。
匹配模式
在项目的任何位置注入 ReuseTabService 类,并设置 mode 属性,或通过 <reuse-tab [mode]="0" /> 重新设置值,包括:
0、Menu(推荐,默认值)
按菜单 (Menu) 配置。
可复用:
{ text:'Dashboard' }
{ text:'Dashboard', reuse: true }
不可复用:
{ text:'Dashboard', reuse: false }
1、MenuForce
按菜单 (Menu) 强制配置。
可复用:
{ text:'Dashboard', reuse: true }
不可复用:
{ text:'Dashboard' }
{ text:'Dashboard', reuse: false }
2、URL
对所有路由有效,可以配合 excludes 过滤无须复用路由。
标签文本
根据以下顺序获取标签文本:
- 使用
ReuseTabService.title = 'new title' 在组件内覆盖文本 - 路由配置中
data 属性中包含 reuseTitle > title - 菜单数据中
text 属性
ReuseTabService 代码示例:
export class DemoReuseTabEditComponent implements OnInit {
id: any;
constructor(private route: ActivatedRoute, private reuseTabService: ReuseTabService) {}
ngOnInit(): void {
this.route.params.subscribe(params => {
this.id = params.id;
this.reuseTabService.title = `编辑 ${this.id}`;
});
}
}
生命周期
路由复用不会触发现Angular组件生命周期钩子(例如:ngOnInit 等),但是往往需要在复用过程中刷新数据,因此提供了两种新生命周期钩子用于临时解决这类问题。
OnReuseInit 接口
_onReuseInit(type?: ReuseHookOnReuseInitType): void;
当目前路由在复用过程中时触发,type 值分别为:
init 当路由复用时refresh 当触发刷新动作时
OnReuseDestroy 接口
当目前路由允许复用且进入新路由时触发。
以 _ 开头希望未来 Angular 会有相应的钩子用于快速替换,一个简单的示例:
import { OnReuseDestroy, OnReuseInit, ReuseHookOnReuseInitType } from '@delon/abc/reuse-tab';
@Component()
export class DemoComponent implements OnReuseInit, OnReuseDestroy {
_onReuseInit(type: ReuseHookOnReuseInitType) {
console.log('_onReuseInit', type);
}
_onReuseDestroy() {
console.log('_onReuseDestroy');
}
}
滚动条位置
开启 keepingScroll 会在复用后恢复之前的滚动条位置,有几项注意细节:
务必使用路由选项 scrollPositionRestoration 来管理滚动条位置
true:表示保持之前滚动条位置false:表示不对滚动条任何操作- 若全站使用路由复用时,则设置
scrollPositionRestoration: 'disabled',避免延迟滚动 - 若部分页面路由复用时,则受限于
scrollPositionRestoration 优先值 ,会有 1ms 延迟恢复滚动条位置
API
ReuseTabService
属性
| 成员 | 说明 | 类型 | 默认值 |
|---|
[max] | 允许最多复用多少个页面,值发生变更时会强制关闭且忽略可关闭条件 | number | 10 |
[mode] | 设置匹配模式 | ReuseTabMatchMode | 0 |
[debug] | 是否Debug模式 | boolean | false |
[keepingScroll] | 保持滚动条位置 | boolean | false |
[keepingScrollContainer] | 保持滚动条容器 | Element | window |
[excludes] | 排除规则,限 mode=URL | RegExp[] | - |
[items] | 获取已缓存的路由 | ReuseTabCached[] | - |
[count] | 获取当前缓存的路由总数 | number | - |
[change] | 订阅缓存变更通知 | Observable<ReuseTabNotify> | - |
[title] | 自定义当前标题 | string | - |
[closable] | 自定义当前 closable 状态 | boolean | - |
方法
| 方法名 | 说明 | 返回类型 |
|---|
index(url) | 获取指定路径缓存所在位置,-1 表示无缓存 | number |
exists(url) | 获取指定路径缓存是否存在 | boolean |
get(url) | 获取指定路径缓存 | ReuseTabCached |
getTitle(url, route?: ActivatedRouteSnapshot) | 获取标题 | string |
clearTitleCached() | 清空自定义标题数据 | void |
getClosable(url, route?: ActivatedRouteSnapshot) | 获取 closable 状态 | string |
clearClosableCached() | 清空 closable 缓存 | void |
remove(url) | 根据URL移除标签,同时触 change remove事件 | void |
move(url, position) | 移动缓存数据,同时触 change move事件 | void |
clear() | 清除所有缓存,同时触 change clear事件 | void |
refresh() | 无任何动作,但会触 change refresh事件 | void |
replace(url) | 强制关闭当前路由(包含不可关闭状态),并重新导航至 newUrl 路由 | void |
reuse-tab
| 成员 | 说明 | 类型 | 默认值 |
|---|
[i18n] | 右击菜单国际化,支持HTML | ReuseContextI18n | - |
[mode] | 设置匹配模式 | ReuseTabMatchMode | 0 |
[debug] | 是否Debug模式 | boolean | false |
[max] | 允许最多复用多少个页面 | number | 10 |
[keepingScroll] | 保持滚动条位置 | boolean | false |
[keepingScrollContainer] | 保持滚动条容器 | `string | Element` |
[excludes] | 排除规则,限 mode=URL | RegExp[] | - |
[allowClose] | 允许关闭 | boolean | true |
[customContextMenu] | 自定义右键菜单 | ReuseCustomContextMenu[] | - |
[tabBarExtraContent] | tab bar 上额外的元素 | TemplateRef<void> | - |
[tabBarStyle] | tab bar 的样式对象 | object | - |
[tabBarGutter] | tabs 之间的间隙 | number | - |
[tabType] | tabs 页签的基本样式 | line, card | line |
[tabMaxWidth] | tabs 页签每一项最大宽度,单位:px | number | - |
[routeParamMatchMode] | 包含路由参数时匹配模式,例如:/view/:id - strict 严格模式 /view/1、/view/2 不同页 - loose 宽松模式 /view/1、/view/2 相同页且只呈现一个标签 - ((future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot) => boolean) 自定义匹配方法 | strict,loose | strict |
[disabled] | 是否禁用 | boolean | false |
[titleRender] | 自定义标题渲染 | TemplateRef<{ $implicit: ReuseItem }> | - |
[storageState] | 是否存储状态,保持最后一次浏览器的状态 | boolean | false |
[canClose] | 关闭时二次校验 | (options: { item: ReuseItem; includeNonCloseable: boolean }) => Observable<boolean> | - |
[trackByFn] | nz-tabs 的 track 函数 | (item: ReuseItem) => item.url | - |
(close) | 关闭回调 | EventEmitter | - |
(change) | 切换时回调,接收的参数至少包含:active、list 两个参数 | EventEmitter | - |
右击菜单
当按下键盘 ctrl 时会强制移除不可关闭项。
ReuseTabCached
| 成员 | 说明 | 类型 | 默认值 |
|---|
[title] | 标题 | string | - |
[url] | URL地址 | string | - |
[closable] | 是否允许关闭 | boolean | - |
ReuseTabNotify
| 成员 | 说明 | 类型 | 默认值 |
|---|
[active] | 事件类型 | title,close,closeRight,clear,move,closable,refresh,add | - |
ReuseContextI18n
| 成员 | 说明 | 类型 | 默认值 |
|---|
[close] | 关闭 | string | - |
[closeOther] | 关闭其它 | string | - |
[closeRight] | 关闭右边 | string | - |
[clear] | 清空 | string | - |
| 成员 | 说明 | 类型 | 默认值 |
|---|
[id] | 唯一标识符 | string | - |
[title] | 标题 | string | - |
[fn] | 处理方法 | (item: ReuseItem, menu: ReuseCustomContextMenu) => void | - |
[disabled] | 是否禁用 | (item: ReuseItem) => boolean | - |
路由data
透过路由 data 附加数据,可以对部分页面提供覆盖全局配置,例如:
// 指定不复路由
{ path: 'p1', component: DemoComponent, data: { reuse: false } }
// 指定标签标题
{ path: 'p1', component: DemoComponent, data: { title: 'New Title' } }
| 成员 | 说明 | 类型 | 默认值 |
|---|
[reuse] | 是否复用 | boolean | - |
[title] | 标题 | string | - |
[titleI18n] | I18n标题Key | string | - |
[reuseClosable] | 是否允许关闭 | boolean | - |
[keepingScroll] | 是否保持滚动条 | boolean | - |
注: 以上数据也可在 Menu 数据中体现。
常见问题
如何Debug?
路由复用会保留组件状态,这可能会带来另一个弊端;复用过程中不会触发Angular生命周期勾子,大部分情况下都能正常运行,有几个常见问题:
OnDestroy 可能会处理一些组件外部(例如:body)的样式等,可以参考生命周期解决。- 开启
debug 模式后会在 console 很多信息这有助于分析路由复用的过程。
Max参数
限定最大复用数据可以减少内存的增长,有几个问题需要注意:
max 参数值发生变更时会强制关闭且忽略可关闭条件- 超出
max 值时,会关掉最先打开 可关闭 的页面,若所有页面都为 不可关闭 则忽略关闭
不支持 QueryString 查询参数
复用采用URL来区分是否同一个页面,而 QueryString 查询参数很容易产生重复性误用,因此不支持查询参数,且在复用过程中会强制忽略掉 QueryString 部分。
多应用缓存处理
使用 provideReuseTabConfig(storeKey: 'newKey') 或通过覆盖 REUSE_TAB_CACHED_MANAGER 改变缓存存储 ,例如在使用微前端(类似ngx-planet)可以重写缓存数据到 window 下来实现数据共享。