如何清理Angular Router解析的数据?方案探讨与痛点分析
确实,Angular Router目前没有提供官方的unresolve钩子来对应解析器的resolve方法,这也是不少开发者碰到的痛点——Router内部明明清楚什么时候不再需要解析出的数据(比如advanceRoute方法里的路由切换逻辑),但并没有把这类状态变化事件暴露给解析器,毕竟解析器只能拿到不可变的路由快照,而非实时的路由事件流。
你当前实现的问题拆解
你现在的思路是在resolve里绑定路由离开事件来触发vm.disconnect(),但这个方案存在不少硬伤:
- 路由匹配逻辑不严谨:没考虑查询参数、辅助路由,而且通过
ActivatedRouteSnapshot拼接URL的方式,没法直接转换成Router.isActive需要的UrlTree,很容易出现判断错误 - 弱耦合风险高:靠字符串URL判断路由是否活跃,和Angular路由的原生解析逻辑绑定太松散,一旦路由结构调整,这个判断就可能失效
- 快照复用导致的异常:路由快照可能被复用,会出现重复绑定清理逻辑或者清理不及时的情况
- 忽略框架配置策略:没有用到Angular可配置的
UrlHandlingStrategy,兼容性差,没法适配自定义的路由处理逻辑 - 目标URL混淆问题:
RouterStateSnapshot.url指向的是最终导航的目标URL,不是当前路由节点的URL,用它来判断当前路由是否活跃完全不对
可行的优化方向
1. 基于路由事件的引用计数策略
可以换成移交式的引用计数管理:把视图模型解析成SharedObservable,然后监听路由的ActivationEnd事件和导航完成事件(NavigationEnd/NavigationCancel/NavigationError)来维护引用计数:
- 当路由激活时(触发
ActivationEnd),给对应视图模型的引用计数+1 - 当路由失效时(对比当前路由树和激活快照的路径,判断该路由节点已被移出),给引用计数-1,计数归零时调用
disconnect() - 这种方式虽然打破了你期望的“连接-断开”对称逻辑,但能避免组件层和Router过度耦合,把所有管理逻辑集中在解析器内部
2. 组件层配合清理(退而求其次的简单方案)
如果不想在解析器里写复杂的路由判断逻辑,也可以把清理逻辑放到组件的ngOnDestroy钩子中:
- 在
resolve方法里,把视图模型和当前路由的唯一标识(比如route.routeConfig.path加上参数的哈希值)关联起来存在一个全局存储里 - 组件在销毁时,通过
ActivatedRoute拿到当前路由的标识,然后从全局存储中找到对应的视图模型,调用disconnect() - 缺点是组件需要感知Router的细节,不符合单一职责原则,但胜在实现简单,适合小型项目
3. 包装Observable实现间接清理
虽然在resolve里没法直接监听ActivatedRoute.data的complete事件,但可以返回一个包装后的Observable,利用订阅销毁的时机触发清理:
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { const vm = RouteValue.useActiveProvider<ViewModelFactoryBase>(route).value(this, (k) => route.paramMap.get(k)); const connect$ = vm.connect(); // 包装Observable,当订阅结束时自动清理 return new Observable(observer => { const subscription = connect$.subscribe(observer); return () => { vm.disconnect(); subscription.unsubscribe(); }; }); }
不过这种方式有个局限性:只有当组件不再订阅ActivatedRoute.data里的这个Observable时才会触发清理,但Angular Router可能会缓存解析数据,导致清理时机被延迟,没法做到路由切换时立刻清理。
理想方案的展望
最理想的情况是Angular官方提供类似RouteLeave的钩子,或者让解析器能接收路由事件流,而不是仅拿到静态快照。目前社区也有相关的讨论,但短期内咱们还是得靠自己实现折中方案来解决问题。
内容的提问来源于stack exchange,提问作者shannon

