Scroll restoration listeners are never removed, so every created router stays in memory
维护者通常 1 天内回复
还没有人认领这个 Issue。
评估
- 难度
- 4/5
- 预计耗时
- 3-5 天
- 新手友好度
- 42/100
- Issue 类型
- 功能
- 描述清晰度
- 基本清楚
- 活跃度
- 活跃
- 技术栈
- typescript
- 领域
- frontend
调研方向
Start with packages/router-core/src/scroll-restoration.ts and the RouterCore constructor, where setup is called; trace the listeners and router subscriptions that need cleanup. Then inspect existing router lifecycle tests and add coverage for repeated dispose() calls and multiple routers sharing scroll restoration. Done means disposing a router removes its listeners and subscriptions without changing history.scrollRestoration while another router still uses it.
由索引模型根据 Issue 内容生成。
描述
Which project does this relate to?
Router
Describe the bug
When scrollRestoration is on, setupScrollRestoration (packages/router-core/src/scroll-restoration.ts) runs once per router, from the RouterCore constructor. It adds three global listeners:
document.addEventListener('scroll', …, true)addEventListener('pagehide', …)addEventListener('pageshow', …)
It keeps no reference to them, and the router has no method to remove them. Each listener closes over the router, so the router (its stores, matches, and the elements in trackedScrollTargets) cannot be garbage collected.
With one router for the life of the page this is fine. It is a leak when an app creates routers more than once, for example:
- an app that renders some views in their own router instance and creates one per view (our case: each page in a stack of sliding panels has its own router, so the page under a panel stays mounted)
- micro-frontends that mount and unmount a router
- tests and HMR that create a new router each time
In our app, after opening and closing 20 panels, getEventListeners(document).scroll.length goes from 1 to 21. Each pagehide listener also writes the scroll cache to sessionStorage, so N routers write it N times on unload.
Steps to reproduce
import { createMemoryHistory, createRootRoute, createRouter } from '@tanstack/react-router'
for (let i = 0; i < 20; i++) {
createRouter({
routeTree: createRootRoute(),
history: createMemoryHistory(),
scrollRestoration: true,
})
}
// Chrome DevTools console:
getEventListeners(document).scroll.length // 20
getEventListeners(window).pagehide.length // 20
Expected behavior
There is a way to release a router that is no longer used: its global listeners are removed and it can be garbage collected.
Proposed fix
setupScrollRestorationkeeps the listeners (and the router subscriptions it adds), and returns a cleanup function.RouterCorestores that cleanup and exposes it as a public method, for examplerouter.dispose(). It is idempotent and does not touchhistory.scrollRestorationif another router still uses scroll restoration.RouterProviderdoes not call it automatically, because the app creates the router outside React and may render it again.
I can open a PR with this and tests. Is dispose() an acceptable name and shape, or do you prefer something else (for example destroy(), or a cleanup returned from router.mount())?
Your environment
@tanstack/react-router1.170.41 (also present onmain, seescroll-restoration.ts)- Chrome 155, macOS
Related: #8024 (bare addEventListener for pagehide), #7956 (history.scrollRestoration mode).
- 主要语言
- TypeScript
- 星标
- 15.2k
- 派生
- 1.9k
- 平均合并
- 1 天 13 小时
- 30 天内合并 PR
- 76
环境准备
在浏览器里用你自己的 GitHub 账号启动这个项目的开发容器。
- 没有 Dockerfile 或 Docker Compose 文件
- 有 Pull Request 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
TanStack/router 的其他 Issue
-
router.startViewTransition leaves the ready/finished promises unhandled, so a skipped transition throws an unhandled AbortError可能已有人在做 @unjica 于 3 天前认领。 未关闭
难度 2/5 1-3 小时 新手友好度 74/100
TanStack/router#8776 · 3 条评论 · 1 个 reaction ·
维护者通常 1 天内回复
-
Router devtools crash when search params contain a BigInt (JSON.stringify in history tracking)可能已有人在做 @A-Raphie 于 7 天前认领。 未关闭
难度 2/5 1-3 小时 新手友好度 76/100
维护者通常 1 天内回复
-
docs: `createLink` has no API reference page; 3 how-to guides link to a missing `#createlink` anchor可能已有人在做 @A-Raphie 于 7 天前认领。 未关闭
难度 2/5 1-3 小时 新手友好度 88/100
维护者通常 1 天内回复
-
Inline scripts are duplicated after hydration when the CSP nonce is sent in a header可能已有人在做 @gabriel-le 于 28 天前认领。 未关闭
难度 1/5 1 小时以内 新手友好度 90/100
维护者通常 1 天内回复
-
start-plugin-core: sitemap lastmod and pages.json lastBuilt are stamped with the build's wall clock (no SOURCE_DATE_EPOCH support)可能已有人在做 @treadnot 于 29 天前认领。 未关闭
难度 2/5 1-3 小时 新手友好度 84/100
维护者通常 1 天内回复
相似的 Issue
-
难度 2/5 1-3 小时 新手友好度 77/100
Clouditera/VulnHunter#57 ·
-
难度 2/5 1-3 小时 新手友好度 72/100
维护者通常 1 天内回复
-
bug bug:behavior clawsweeper:bulk-filed clawsweeper:fix-shape-clear clawsweeper:no-new-fix-pr clawsweeper:source-repro impact:auth-provider issue-rating: 🦞 diamond lobster P2
难度 2/5 1-3 小时 新手友好度 76/100
openclaw/openclaw#185297 · 1 条评论 · 1 个 reaction ·
维护者通常 1 天内回复
-
bug
难度 2/5 1-3 小时 新手友好度 62/100
-
bug needs-triage
难度 2/5 1-3 小时 新手友好度 76/100
apex-dev-tools/apex-log-parser#199 ·
维护者通常 1 天内回复