angular 路由组件缓存复用(类似于vue的keep-alive指令)

摘要:angular提供了路由策略来实现对组件的缓存和复用。我们使用的多级嵌套路由在切换的时,父级路由出口的实例不会重新实例化。就是angular内部使用默认的路由复用策略实现的,这点在看完下面的流程分析就明白了。

类似于vue的keep-alive指令一样,在路由切换的时候,不用销毁和重新实例化组件。

angular提供了路由策略来实现对组件的缓存和复用。我们使用的多级嵌套路由在切换的时,父级路由出口的实例不会重新实例化。就是angular内部使用默认的路由复用策略实现的,这点在看完下面的流程分析就明白了。


一、概念

1、路由树

我们知道,在配置了路由导航的angular应用会形成一棵应用的路由树


应用会从根开始逐级去匹配每一级的路由节点和routeConfig,并检测实例化路由组件,其中routeConfig涵盖树里的每一个节点,包括懒加载路由


2、路由复用策略

RouteReuseStrategy是angular提供的一个路由复用策略,暴露了简单的接口

abstract  class  RouteReuseStrategy {
  // 确定是否应重用路由
  abstract  shouldReuseRoute(future:  ActivatedRouteSnapshot, curr:  ActivatedRouteSnapshot): boolean
  // 存储分离的路由 存储“null”值应删除以前存储的值
  abstract  store(route:  ActivatedRouteSnapshot, handle:  DetachedRouteHandle):  void
  // 确定是否应重用路由
  abstract  shouldAttach(route:  ActivatedRouteSnapshot): boolean
  // 检索以前存储的路由
  abstract  retrieve(route:  ActivatedRouteSnapshot):  DetachedRouteHandle  |  null
  // 确定是否应分离此路由(及其子树)以供以后重用。若 `true` 会触发 `store
  // 离开的路由,是否储存
  abstract  shouldDetach(route:  ActivatedRouteSnapshot): boolean
}


二、方法解析

1、shouldReuseRoute

检测是否复用路由,该方法根据返回值来决定是否继续调用,如果返回值为true则表示当前节点层级路由复用,将继续下一路由节点调用,入参为的future和curr不确定,每次都交叉传入;否则,则停止调用,表示从这个节点开始将不再复用。
两个路由切换的时候是从“路由树”的根开始从上往下层级依次比较和调用的,并且两边每次比较的都是同一层级的路由节点配置。root路由节点调用一次,非root路由节点调用两次这个方法,第一次比较父级节点,第二次比较当前节点。

还是以上面的路由树为例,它的检测层级是这样的:


对比图示,方法的每一次调用时比较的都是同一层级的路由配置节点,就是像图中被横线穿在一起的那些一样,即入参的future和curr是同级的。

举个例子,shouldReuseRoute方法的常见实现为:

shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
  return  future.routeConfig  === curr.routeConfig;
}

这时当路由从“main/cop/web/pc”切换到“main/cop/fan/list/group”的调用顺序是这样的:

1
root --> main --> web / fan (返回false)

即到第3层的时候routeConfig不一样,返回false,调用结束,得到不复用的“分叉路由点”

这个方法得到的结果很重要,将作为其他好几个方法的基础

2、retrieve

紧接着shouldReuseRoute方法返回false的节点调用,入参route即是当前层级路由不需要复用。以上个例子说明,此时的route是main/cop/fan/的路由节点。 retrieve调用根据返回结果来决定是否继续调用:如果返回的是null,当前路由对应的组件会实例化,并继续对其子级路由调用retrieve方法,直到遇到缓存路由或到末级路由

在本次路由还原时也会调用,用来获取缓存示例

3、shouldDetach

用来判断刚刚离开的上一个路由是否复用,其调用的时机也是当前层级路由不需要复用,shouldReuseRoute方法返回false的时候。以上个例子说明,首次调用的入参route是main/cop/web/的路由节点。 shouldDetach方法根据返回结果来决定是否继续调用:如果返回的是false,则继续下一层级调用该方法,当前路由对应的组件会实例化,并继续对其子级路由调用retrieve方法,直到返回true或者是最末级路由后才结束。

4、store

紧接着shouldDetach方法返回true的时候调用,存储需要被缓存的那一级路由的DetachedRouteHandle;若没有返回true的则不调用。 以上个例子说明,若我们设置了main/cop/web/pc的keep=true,此时的入参route是main/cop/web/pc节点,存储的是它的实例对象。

无论路径上有几个可以被缓存的路由节点,被存储的只有有一个,就是Detach第一次返回true的那次 在本次路由还原后也会调用一次此方法存储实例

5、shouldAttach

判断是否允许还原路由对象及其子对象,调用时机是当前层级路由不需要复用的时候,即shouldReuseRoute()返回false的时候,而且,并不是所有的路由层级都是有组件实例的,只有包含component的route才会触发shouldAttach。 如果反回false,将继续到当前路由的下一带有component的路由层级调用shouldAttach,直到返回true或者是最末级路由后才结束。 当shouldAttach返回true时就调用一次retrieve方法和store方法


三、调用顺序

shouldReuseRoute -> retrieve -> shouldDetach -> store -> shouldAttach -
-> retrieve(若shouldAttach返回true) -> store(若shouldAttach返回true) 



四、应用

export class RouteMsg {
constructor(public type: string, public url: string, public route: ActivatedRouteSnapshot) { }
}

export class AppReuseStrategy implements RouteReuseStrategy {

private static routeText$ = new Subject<RouteMsg>()
private static handlers: Map<string, DetachedRouteHandle> = new Map()
public static routeReuseEvent = AppReuseStrategy.routeText$.asObservable()
/**
* 确定是否应分离此路由(及其子树)以供以后重用。若 `true` 会触发 `store
* @param route
* @returns
*/
shouldDetach(route: ActivatedRouteSnapshot): boolean {
if (this.hasInValidRoute(route)) {
return false
}
AppReuseStrategy.routeText$.next(new RouteMsg('detach', this.getUrl(route), route))
return Boolean(route.data.keep)
}
/**
* 存储分离的路线 存储“null”值应删除以前存储的值
* @param route
* @param handle
*/
store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
AppReuseStrategy.handlers.set(this.getUrl(route), handle)
}
/**
* 确定是否应重新附着此路由(及其子树)
* @param route
* @returns
*/
shouldAttach(route: ActivatedRouteSnapshot): boolean {
if (this.hasInValidRoute(route)) {
return false
}
AppReuseStrategy.routeText$.next(new RouteMsg('attach', this.getUrl(route), route))
return AppReuseStrategy.handlers.has(this.getUrl(route))
}
/**
* 检索以前存储的路由
* @param route
* @returns
*/
retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle | null {
if (this.hasInValidRoute(route)) return null
return AppReuseStrategy.handlers.get(this.getUrl(route))||null
}
/**
* 确定是否应重用路由
* @param future
* @param curr
* @returns
*/
shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
let ret = future.routeConfig === curr.routeConfig
if (!ret) return false

const path = ((future.routeConfig && future.routeConfig.path) || '') as string
if (path.length > 0 && ~path.indexOf(':')) {
ret = this.getUrl(future) === this.getUrl(curr)
}
return ret
}

hasInValidRoute(route: ActivatedRouteSnapshot): boolean {
return !route.routeConfig || !!route.routeConfig.loadChildren || !!route.routeConfig.children;
}
getTruthRoute(route: ActivatedRouteSnapshot): ActivatedRouteSnapshot {
let next = route;
while (next.firstChild) next = next.firstChild;
return next;
}
/**
* 根据快照获取URL地址
*/
getUrl(route: ActivatedRouteSnapshot): string {
let next = this.getTruthRoute(route);
const segments: string[] = [];
while (next) {
segments.push(next.url.join('/'));
next = next.parent!;
}
const url = `/${segments
.filter(i => i)
.reverse()
.join('/')}`;
return url;
}
}

//在对应组件订阅该对象
//此时组件不再重新初始化,以前放在Init和Destroy钩子里做的事情可能需要考虑找个时机来做,可以使rxjs订阅来做,修改策略代码,增加subject,
AppReuseStrategy.routeReuseEvent.pipe(
takeUntil(this.unsubscribe$)
).subscribe(res => {
if (res.type == 'detach') {

} else if (this.router.url.includes(res.url)) {
if (res.type == 'attach') {

}
}
})
}
作者:liuk123
来源:http://www.cicode.cn/blog/detail/412

本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!

链接: https://shenqiku.cn/article/FLY_12572