likes
comments
collection
share

这就是为什么Angular这么少人用了,连个路由缓存都这么难配置!!

作者站长头像
站长
· 阅读数 55

起因

要不是我入职的这家公司是Angular技术栈,我才不会去学Angular!!这一个路由缓存都百度了一上午,才差不多完全搞懂!

需求

某天领导说我们的业务系统(Angular技术栈)要加上路由缓存,在点击菜单或者tab标签页的时候能够实现状态保持。

那不是分分钟的事,想想Vue的KeepAlive,实现一个路由缓存比呼吸还简单,Angular再难还能难到哪里去,随即百度~

这就是为什么Angular这么少人用了,连个路由缓存都这么难配置!!

原来如此

  1. 第一步需要有一个类,继承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
}
  1. 很快我就写出了第一版:

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('/');
  }
}
  1. 第三步在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们)。只能自己实现

这就是为什么Angular这么少人用了,连个路由缓存都这么难配置!!

我们可以在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,真是造孽啊!

这就是为什么Angular这么少人用了,连个路由缓存都这么难配置!!

转载自:https://juejin.cn/post/7372025897158017074
评论
请登录