这就是为什么Angular这么少人用了,连个路由缓存都这么难配置!!
起因
要不是我入职的这家公司是Angular技术栈,我才不会去学Angular!!这一个路由缓存都百度了一上午,才差不多完全搞懂!
需求
某天领导说我们的业务系统(Angular技术栈)要加上路由缓存,在点击菜单或者tab标签页的时候能够实现状态保持。
那不是分分钟的事,想想Vue的KeepAlive
,实现一个路由缓存比呼吸还简单,Angular再难还能难到哪里去,随即百度~
原来如此
- 第一步需要有一个类,继承
RouteReuseStrategy
类,这个类需要实现这几个方法:
abstract class RouteReuseStrategy {
// 判断是否复用路由
abstract shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean
// 存储路由快照&组件当前实例对象
abstract store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void
// 判断是否允许还原路由对象及其子对象
abstract shouldAttach(route: ActivatedRouteSnapshot): boolean
// 获取实例对象,决定是否实例化还是使用缓存
abstract retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle | null
// 判断路由是否允许复用
abstract shouldDetach(route: ActivatedRouteSnapshot): boolean
}
-
很快我就写出了第一版:
import { ActivatedRouteSnapshot, DetachedRouteHandle, RouteReuseStrategy } from '@angular/router';
export class CustomReuseStrategy implements RouteReuseStrategy {
private handlers: { [key: string]: DetachedRouteHandle } = {};
private maxCacheCount = 30;
private storedRoutes: string[] = [];
shouldDetach(route: ActivatedRouteSnapshot): boolean {
return true;
}
store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
const url = this.getRouteUrl(route);
if (handle) {
if (Object.keys(this.handlers).length >= this.maxCacheCount) {
const oldestUrl = this.storedRoutes.shift();
if (oldestUrl) {
const oldestHandle = this.handlers[oldestUrl];
// console.log("🚀 ~ oldestHandle:", oldestHandle)
delete this.handlers[oldestUrl];
//
}
}
this.handlers[url] = handle;
this.storedRoutes.push(url);
}
}
shouldAttach(route: ActivatedRouteSnapshot): boolean {
return !!this.handlers[this.getRouteUrl(route)];
}
retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
return this.handlers[this.getRouteUrl(route)];
}
shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
return future.routeConfig === curr.routeConfig
}
private getRouteUrl(route: ActivatedRouteSnapshot) {
return route.pathFromRoot.map(v => v.url.map(segment => segment.toString()).join('/')).join('/');
}
}
- 第三步在
appModule
的provides数组里使用useClass替换默认路由策略。
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: GlobalHttpInterceptor,
multi: true
}
],
坑点
1. 无法缓存,又是AI又是百度
shouldReuseRoute检测是否复用路由,该方法根据返回值来决定是否继续调用,如果返回值为true则表示当前节点层级路由复用,将继续下一路由节点调用,入参为的future和curr不确定,每次都交叉传入;否则,则停止调用,表示从这个节点开始将不再复用。 两个路由路径切换的时候是从“路由树”的根开始从上往下层级依次比较和调用的,并且两边每次比较的都是同一层级的路由节点配置。root路由节点调用一次,非root路由节点调用两次这个方法,第一次比较父级节点,第二次比较当前节点。
这时当路由从“a/b/c/d”切换到“a/b/cc/dd/ee”的调用顺序是这样的:
root --> a --> c / cc (返回false)
所以需要改造为这样对比:
shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
return future.routeConfig === curr.routeConfig &&
JSON.stringify(future.params) === JSON.stringify(curr.params)
}
2. 产品经理:“能不能在我点击tab关闭当前页面的时候,同时将缓存清掉啊,下次打开重新加载这个页面”。嗯,很合理。
- 这个类需要清除缓存的方法,同时需要因为这个类是单例的,需要将这个类改造,使之可以通过`CustomReuseStrategy.clearCache(key)`调用
- 在调用这个清除缓存方法之前需要改造store方法,在类中声明一个waitDelete用于接受不缓存的key,同时改造store方法,当`url === key`的时候不缓存
实现:
import { ActivatedRouteSnapshot, DetachedRouteHandle, RouteReuseStrategy } from '@angular/router';
export class CustomReuseStrategy implements RouteReuseStrategy {
public static handlers: { [key: string]: DetachedRouteHandle } = {};
private maxCacheCount = 30;
public static storedRoutes: string[] = [];
public static waitDelete: string | null;
shouldDetach(route: ActivatedRouteSnapshot): boolean {
return true;
}
store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
const url = this.getRouteUrl(route);
// 如果待删除的是当前路由则不存储快照
if (url.includes(CustomReuseStrategy.waitDelete)) {
CustomReuseStrategy.waitDelete = null;
return;
}
if (handle) {
if (Object.keys(CustomReuseStrategy.handlers).length >= this.maxCacheCount) {
const oldestUrl = CustomReuseStrategy.storedRoutes.shift();
if (oldestUrl) {
const oldestHandle = CustomReuseStrategy.handlers[oldestUrl];
// console.log("🚀 ~ oldestHandle:", oldestHandle)
delete CustomReuseStrategy.handlers[oldestUrl];
//
}
}
CustomReuseStrategy.handlers[url] = handle;
if (!CustomReuseStrategy.storedRoutes.includes(url)) {
CustomReuseStrategy.storedRoutes.push(url);
}
}
}
shouldAttach(route: ActivatedRouteSnapshot): boolean {
return !!CustomReuseStrategy.handlers[this.getRouteUrl(route)];
}
retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
return CustomReuseStrategy.handlers[this.getRouteUrl(route)];
}
shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
return future.routeConfig === curr.routeConfig &&
JSON.stringify(future.params) === JSON.stringify(curr.params)
}
private getRouteUrl(route: ActivatedRouteSnapshot) {
return route.pathFromRoot.map(v => v.url.map(segment => segment.toString()).join('/')).join('/');
}
public static clearCache(dashboardKey: string) {
const ids = CustomReuseStrategy.storedRoutes.findIndex(key => key.includes(dashboardKey));
if (ids > -1) {
delete CustomReuseStrategy.handlers[CustomReuseStrategy.storedRoutes[ids]];
CustomReuseStrategy.storedRoutes.splice(ids, 1)
}
}
}
在需要清除路由缓存的时候这样调用:
CustomReuseStrategy.waitDelete = key;
CustomReuseStrategy.clearCache(key);
OK done!
3. 测试小姐姐:“为什么我在其他页面更新了数据,再导航到目标页面,为什么页面数据没有更新啊??”。好吧!!开始继续改造:
百度了一波好像官方并没有提供类似于VueonActivated
,onDeactivated
钩子(突然感觉好羡慕Vue们)。只能自己实现
我们可以在store
组件失活的函数里拿到组件实例:
if (handle &&
handle.componentRef &&
handle.componentRef.instance &&
handle.componentRef.instance.deactivated) {
// 在组件失活的时候调用组件的 deactivated 方法,如果有deactivated方法
handle.componentRef?.instance?.deactivated();
}
可以在retrieve
组件激活的函数里拿到组件实例:
if (handle &&
handle.componentRef &&
handle.componentRef.instance &&
handle.componentRef.instance.activated) {
// 在组件失活的时候调用组件的 activated 方法,如果有activated方法
handle.componentRef?.instance?.activated();
}
OK done!!
小结
一个小小的路由缓存,竟然要学这么多api,真是造孽啊!
转载自:https://juejin.cn/post/7372025897158017074