TanStack Router 路由事件订阅指南:用 router.subscribe 精准感知导航生命周期
2026/9/14 19:05:26 网站建设 项目流程

TanStack Router 路由事件订阅指南:用 router.subscribe 精准感知导航生命周期

【免费下载链接】router🤖 A client-first, server-capable, fully type-safe router and full-stack framework for the web (React and more).项目地址: https://gitcode.com/GitHub_Trending/ro/router

导读

TanStack Router 通过router.subscribe对外暴露完整的路由生命周期事件,让开发者可以以命令式方式监听"导航开始、路由加载、组件挂载、导航完成"等关键节点。这篇指南将带你掌握router.subscribe的完整用法:事件列表、触发顺序、事件载荷(payload)字段语义,以及埋点统计、外部状态清理、DOM 操作等真实场景的落地写法;同时结合本仓库 router-core 的源码实现,说明这些事件在内部到底是如何被计算和发射的。

基本用法:订阅与取消订阅

router.subscribe接收两个参数:事件名监听器函数,并返回一个unsubscribe函数用于移除监听:

const unsubscribe = router.subscribe('onResolved', (event) => { console.info('Navigation finished:', event.toLocation.href) }) // 稍后清理监听器 unsubscribe()

从源码看,subscribe的实现非常轻量(packages/router-core/src/router.ts#L1407-L1418):它将{ eventType, fn }包装成一个 listener 放入内部的subscribers集合(Set),返回的闭包负责从集合中删除该 listener。这意味着:

  • 同一事件可以注册多个监听器,它们都会被依次调用;
  • 返回的unsubscribe幂等且安全的——即使重复调用也不会报错;
  • 监听器内部抛出的异常会被emit捕获并console.error(router.ts#L1420-L1430),不会中断其他监听器,也不会影响路由本身的导航流程。

何时该用 router.subscribe

router.subscribe最适合那些"需要观察导航、但不需要驱动渲染"的命令式集成场景:

  • 埋点与页面访问(pageview)统计
  • 重置外部缓存或 mutation 状态
  • 记录导航耗时与过渡过程
  • 在路由渲染完成后执行依赖 DOM 的逻辑(如聚焦标题、滚动定位)

如果你需要的是响应式的 UI 更新,则应当优先使用框架 Hooks(如useRouterStateuseSearchuseParams),而不是手动订阅。订阅是命令式的一次性回调,不会触发组件重渲染;Hooks 则会与渲染周期绑定,自动在状态变化时重渲染。

可用事件一览

TanStack Router 会发射以下生命周期事件:

事件名触发时机
onBeforeNavigate一次导航即将开始之前
onBeforeLoad路由加载开始之前
onLoad下一个 location 已提交、路由匹配(matches)已加载完成之后
onBeforeRouteMount加载完成后、路由组件即将挂载之前
onResolved导航完全解析(resolve)之后
onRendered路由渲染完成之后

此外,事件类型中还存在一个onInjectedHtml事件(仅在服务端 SSR 场景由ssr-server触发,用于注入 HTML 流时回调),完整的事件载荷类型定义见 RouterEventsType 类型文档。每个事件的 payload 都以事件名作为type字段,可用于在同一个监听函数内进行可辨识联合(discriminated union)判断:

router.subscribe('onResolved', (event) => { if (event.type === 'onResolved') { // 类型被收窄为 onResolved 的载荷 } })

典型事件流

对于一次正常导航,事件通常按以下顺序流动:

  1. onBeforeNavigate
  2. onBeforeLoad
  3. onLoad
  4. onBeforeRouteMount
  5. onResolved
  6. onRendered

在客户端实现中(packages/router-core/src/load-client.ts#L1952-L1958),onBeforeNavigateonBeforeLoad在路由匹配与资源获取之前同步发射;而在匹配加载完成、location 提交之后(load-client.ts#L1877-L1909),依次发射onLoadonBeforeRouteMount,并在resolvedLocation更新、router 状态回到idle后发射onResolvedonRendered

服务端(SSR)路径同样会发射onBeforeNavigateonBeforeLoad(见 packages/router-core/src/load-server.ts#L891-L894),因此基于这两个事件编写的"导航开始"类逻辑在服务端渲染时也能生效。

需要注意:onBeforeNavigateonBeforeLoad之间存在一个 preflight 中断检查(load-client.ts#L1955-L1958)——如果导航被更快的后续导航取代(abort),onBeforeLoad可能不会发射。因此,若必须在"加载开始"时做副作用,建议以onBeforeNavigate为准,或同时兼容两个事件。

你通常不需要监听所有事件。经验法则:

  • onBeforeNavigateonBeforeLoad观察导航开始
  • onResolved埋点统计与导航完成后的清理
  • onRendered依赖 DOM 的工作

事件载荷:location 变化元数据

导航类事件都会携带描述"从哪到哪、发生了什么变化"的元数据:

const unsubscribe = router.subscribe('onBeforeNavigate', (event) => { console.info({ from: event.fromLocation?.href, to: event.toLocation.href, pathChanged: event.pathChanged, hrefChanged: event.hrefChanged, hashChanged: event.hashChanged, }) })

几个关键细节:

  • fromLocation首次加载时可能是undefined(因为不存在上一个已解析的 location);
  • pathChanged表示pathname 是否变化
  • hrefChanged覆盖pathname、search、hash 的任意变化
  • hashChanged用于区分仅 hash 变化的导航(例如锚点跳转)。

这些布尔标志的语义直接来自源码中的getLocationChangeInfo(packages/router-core/src/router.ts#L926-L937):

export function getLocationChangeInfo(location, resolvedLocation) { return { fromLocation: resolvedLocation, toLocation: location, pathChanged: resolvedLocation?.pathname !== location.pathname, hrefChanged: resolvedLocation?.href !== location.href, hashChanged: resolvedLocation?.hash !== location.hash, } }

即:pathChanged是比较新旧pathnamehrefChanged是比较完整hrefhashChanged是比较hashfromLocation/toLocation的类型为ParsedLocation(解析后的强类型 location,包含pathnamesearchhashhrefstate等字段,详见 ParsedLocation 类型文档),因此你可以在监听器里安全地读取这些结构化字段,而不必手动解析 URL 字符串。

常见模式

跟踪页面访问(pageview)

onResolved是埋点的首选事件,因为它在导航完全结束后才触发,此时toLocation即最终落地的页面:

const unsubscribe = router.subscribe('onResolved', ({ toLocation }) => { analytics.track('page_view', { path: toLocation.pathname, href: toLocation.href, }) })

清除外部 mutation 状态

如果你使用的 mutation 库没有按 key 隔离的 mutation 状态(例如一个全局的 mutation cache),可以在导航后清理——但只应在路径确实变化时清理,避免同路径下的 search/hash 变化误伤:

const unsubscribe = router.subscribe('onResolved', ({ pathChanged }) => { if (pathChanged) { mutationCache.clear() } })

执行依赖 DOM 的逻辑

当副作用依赖"新路由内容已经在 DOM 中"时,使用onRendered

const unsubscribe = router.subscribe('onRendered', ({ toLocation }) => { focusPageHeading(toLocation.pathname) })

onRendered的发射条件带有rendered判断(load-client.ts#L1908-L1910),确保只有真正完成渲染的提交才会触发,因此比onResolved更适合 DOM 操作。事实上,TanStack Router 内置的滚动恢复功能正是依赖onBeforeLoad记录滚动位置、onRendered恢复滚动位置(见 packages/router-core/src/scroll-restoration.ts#L216 与 scroll-restoration.ts#L239),可以作为"官方同款"的订阅范例参考。

在组件中正确取消订阅

如果你在组件或框架的 effect 中订阅,一定要把返回的unsubscribe函数作为清理函数返回,确保组件卸载时监听器被移除,避免内存泄漏与重复触发:

import { useEffect } from 'react' import { useRouter } from '@tanstack/react-router' function PageViewTracker() { const router = useRouter() useEffect(() => { return router.subscribe('onResolved', ({ toLocation }) => { analytics.track('page_view', { path: toLocation.pathname }) }) }, [router]) return null }

由于subscribe返回的正是清理函数本身,直接return router.subscribe(...)即可满足 React 对 effect 清理函数的要求。

与路由 Hooks 的分工

router.subscribe解决的是"过程观察"问题,而useRouterStateuseSearchuseParams等 Hooks 解决的是"状态渲染"问题:

  • 需要渲染当前 location、search、params → 用 Hooks(useRouterStateHook、useSearchHook、useParamsHook);
  • 需要在导航过程中执行一次性副作用→ 用router.subscribe
  • 需要读取当前 router 状态快照(如resolvedLocationstatus)→ 参考 RouterState 类型文档 与 Router 类型文档。

小结

router.subscribe是 TanStack Router 面向命令式集成场景的官方事件通道,覆盖了从导航开始到渲染完成的完整生命周期。通过本文介绍的六个事件、标准事件流、payload 字段语义与三类常见模式,你可以把埋点统计、缓存清理、DOM 操作等副作用干净地挂接到路由上;结合 router-core 源码 中subscribe/emit/getLocationChangeInfo的实现,还能准确预判不同导航(首次加载、路径变化、hash-only 变化、被取代的导航)下的事件行为。

如果你的侧重点是在导航过程中同步修改数据(例如加载完成后的数据变更流程),可以进一步阅读 数据变更指南,它与本文的路由事件机制共同构成 TanStack Router 导航闭环的两大观测入口。

【免费下载链接】router🤖 A client-first, server-capable, fully type-safe router and full-stack framework for the web (React and more).项目地址: https://gitcode.com/GitHub_Trending/ro/router

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询