
tRPC Next.js 请求取消实战使用abortOnUnmount在组件卸载时中止 Procedure 调用【免费下载链接】trpc♀️ Move Fast and Break Nothing. End-to-end typesafe APIs made easy.项目地址: https://gitcode.com/GitHub_Trending/tr/trpc在 tRPC10.x的 Next.js 集成中页面组件卸载时正在飞行中的 RPC 请求默认不会被取消而是继续占用网络与服务器资源。本文针对 aborting-procedures.md 展开讲解如何通过abortOnUnmount在「全局」和「单次请求」两个层级开启卸载即中止行为并结合trpc/next、trpc/react-query的实际源码说明该选项从配置到真正触发AbortController的完整底层链路。读完你将能精确控制 Next.js 页面中 tRPC 查询的生命周期避免组件卸载后产生无效请求。为什么默认不取消先理解 tRPC 与 React Query 的关系在trpc/next中tRPC 的 hooks 建立在tanstack/react-query之上查询的数据获取、缓存、重试与去重由 React Query 负责而真正发起 HTTP 调用的则是trpc/client。createTRPCNext通过createRootHooks生成一套绑定到 tRPC Router 类型的 hooks见 createTRPCNext.tsx页面里调用的trpc.post.byId.useQuery(...)最终都会落到 React Query 的useQuery上。React Query 组件卸载时默认会把该查询标记为取消但在 tRPC 的封装中取消请求需要把 React Query 的取消信号透传给底层 HTTP 客户端才能真正中断网络连接。出于保守设计tRPC 选择默认不进行卸载取消文档中的原文说明是By default, tRPC does not cancel requests on unmount.从源码看这个不取消的默认值在 Provider 层级被硬编码为false。在 createHooksInternal.tsx 中const TRPCProvider: TRPCProviderTRouter, TSSRContext (props) { const { abortOnUnmount false, queryClient, ssrContext } props;也就是说即使不配置任何东西应用也能正常工作——只是卸载时遗留的请求会继续执行。如果你希望主动终止这类请求就需要显式打开abortOnUnmount。全局开启在config()回调中配置abortOnUnmount最简单的方式是在创建createTRPCNext客户端的config()回调中全局开启。这正是文档给出的典型写法// target: esnext // ---cut--- // filename: utils.ts // noErrors import { createTRPCNext } from trpc/next; export const trpc createTRPCNextAppRouter({ config() { return { // ... abortOnUnmount: true, }; }, });这段代码说明了两件事abortOnUnmount是config()返回对象中的一个合法字段它和url、links、transformer等客户端配置并列它是可选字段类型为boolean不传即保持默认false。在类型层面该字段被定义在WithTRPCConfig中。WithTRPCConfig由 tRPC 客户端配置CreateTRPCClientOptionsTRouter与 React Query 的CreateTRPCReactQueryClientConfig交叉并额外增加了一个abortOnUnmount?: boolean属性见 withTRPC.tsxexport type WithTRPCConfigTRouter extends AnyRouter CreateTRPCClientOptionsTRouter CreateTRPCReactQueryClientConfig { abortOnUnmount?: boolean; };运行时withTRPC会把这个配置读取出来并传给trpc.Provider。注意其中有两个关键点组件树由WithTRPC包装配置在useState初始化时只读取一次config.abortOnUnmount见 withTRPC.tsx因此后续热更新配置不会影响已经挂载的 Provider传递给 Provider 时对缺失值做了兜底处理(prepassProps as any).abortOnUnmount ?? false见 withTRPC.tsx保证即使配置里漏写了该字段也不会因为undefined导致行为不确定。提示当前仓库内trpc/next的最新源码已将该配置项迁移到createTRPCNext之上更通用的 hooks 层但 10.x 版本config()内传入的写法保持兼容。若你正在从 10.x 向 v11 迁移可参考迁移目录中的相关指南migrate-from-v10-to-v11.mdx。单次请求覆盖在 hook 调用处传入trpc.abortOnUnmount全局开启后如果个别请求需要特赦例如某个轮询查询希望卸载后仍在服务端跑完或者全局未开启但你只想对某一个查询启用取消都可以在 hook 选项里通过trpc命名空间做请求级覆盖。文档中针对文章详情页PostViewPage的示例// target: esnext // ---cut--- // filename: pages/posts/[id].tsx // noErrors import { trpc } from ~/utils/trpc; const PostViewPage: NextPageWithLayout () { const id useRouter().query.id as string; const postQuery trpc.post.byId.useQuery({ id }, { trpc: { abortOnUnmount: true } }); return (...) }这里useQuery的第二个参数是 React Query 的选项对象tRPC 把自有选项统一收纳在trpc字段下避免与 React Query 原生选项enabled、staleTime、refetchInterval等冲突。需要强调的是abortOnUnmount是逐请求求值的——同一个组件内不同的查询可以有不同的取值互不影响。底层的三级优先级请求级 客户端配置 Provider 默认值从源码看到底要不要取消并不是简单的一处布尔判断而是按明确优先级逐层解析的。以useQuery的实现为例createHooksInternal.tsx 中的解析逻辑如下const shouldAbortOnUnmount opts?.trpc?.abortOnUnmount ?? config?.abortOnUnmount ?? abortOnUnmount;解析优先级从高到低是opts.trpc.abortOnUnmount——当前这个 hook 调用传入的请求级选项优先级最高config.abortOnUnmount——config()返回的客户端配置在 React Query 的 queryDefaults 层面解析abortOnUnmount——来自trpc.Provider上下文的值也就是上面 Provider 里默认的false。其余 hooks 也遵循同样的解析原则。例如useInfiniteQuery中为见 createHooksInternal.tsx// request option should take priority over global const shouldAbortOnUnmount opts?.trpc?.abortOnUnmount ?? abortOnUnmount;值得注意的是??空值合并运算符的使用只有当高层选项为null/undefined时才向下一层取值如果你显式写了abortOnUnmount: false它会正确覆盖全局的true。因此全局关闭 个别开启与全局开启 个别关闭两种组合都能精确实现。信号如何真正生效从 React Query 的signal到 HTTP 中止配置解析只是第一步真正有价值的问题是打开这个开关后请求是如何被取消的答案藏在useQuery的queryFn构造逻辑中。当shouldAbortOnUnmount为true时tRPC 会把 React Query 传入queryFunctionContext的signal塞进 tRPC 客户端请求选项否则显式传signal: null确保不继承任何取消能力见 createHooksInternal.tsxconst hook __useQuery( { ...ssrOpts, queryKey: queryKey as any, queryFn: async (queryFunctionContext) { const actualOpts { ...ssrOpts, trpc: { ...ssrOpts?.trpc, ...(shouldAbortOnUnmount ? { signal: queryFunctionContext.signal } : { signal: null }), }, }; const result await client.query(...getClientArgs(queryKey, actualOpts)); // ... return result; }, }, queryClient, );链路可以完整地概括为四步React Query管理查询生命周期。组件卸载时React Query 会调用其内部AbortController中止该查询并通过queryFunctionContext.signal通知数据获取函数tRPC 中转。useQuery/useInfiniteQuery/useSuspenseQuery/usePrefetchQuery等 hooks 依据上文的三级优先级算出shouldAbortOnUnmount决定是否把这个signal放入传给client.query的选项里客户端发起请求。trpc/client收到带signal的请求选项后在底层 HTTP link如httpBatchLink/httpLink中将其绑定到fetch的请求信号上网络层中止。一旦 signal 被触发浏览器立即abort()底层的fetch正在传输的请求被中断请求也自然不会再更新组件状态此时组件已卸载。换句话说abortOnUnmount: true的本质是把 React Query 的查询取消信号桥接给 tRPC 客户端而取消本身由浏览器/运行时的AbortControllerfetch机制兜底实现。仓库中针对该行为的回归测试位于 abortOnUnmount.test.tsx它验证了卸载后请求确实会被中止。适用面与边界哪些 hooks 支持、哪些不支持结合源码中对abortOnUnmount的读取位置createHooksInternal.tsx可以推断该开关覆盖了大部分查询类hooks包括useQuery含useSuspenseQuery系列useInfiniteQuery含对应的 prefetch 变体usePrefetchQuery/usePrefetchInfiniteQuery它们在 Provider 内共享同一个context.abortOnUnmount因此开启后行为一致。同时需要明确两点限制变更mutation不受该选项控制。abortOnUnmount只作用于卸载后仍可能残留的查询请求手动触发的 mutation 由useMutation().mutate()明确调用组件卸载时本就不应该用同一个开关静默取消以免引发状态不一致真正的取消依赖网络层支持。该机制最终落实为对fetch的 abort。如果你使用的是自定义 transport、SSE/WebSocket 订阅或第三方非标准 fetch 实现其取消语义取决于对应实现是否监听 signal不能仅凭该开关保证请求在服务端也会立刻中断。服务端收到中止信号后过程procedure是否停止执行还取决于服务端实现与运行时对连接断开的处理。最佳实践小结默认保持关闭是合理选择并非所有请求都适合卸载即取消——例如后台统计、埋点、或写入型预热的请求可能希望发出去了就别管且 React Query 本身对已解析缓存有保护残留请求一般不引发可见 bug全局开启适合快速离开型页面信息流、列表详情这类用户频繁进出、请求耗时长、结果不再被展示的页面全局abortOnUnmount: true能立刻释放连接请求级覆盖用于精确治理在config()里统一开或关再在个别useQuery的trpc.abortOnUnmount上做反向覆盖是兼顾整洁与弹性的组合用法留意 SSR 阶段config()在客户端与 SSR 都可能执行Provider 传入时对空值做了?? false兜底withTRPC.tsx。SSR prepass 阶段ssr: true配合ssrPrepass的请求由服务端预取流程管理本选项主要面向客户端卸载场景。一句话收束abortOnUnmount是 tRPC × React Query 在 Next.js 中把查询生命周期与组件生命周期对齐的官方开关理解它的三级优先级与 signal 透传链路你就能在不引入额外请求管理库的前提下精确控制每次 RPC 调用的取消时机。【免费下载链接】trpc♀️ Move Fast and Break Nothing. End-to-end typesafe APIs made easy.项目地址: https://gitcode.com/GitHub_Trending/tr/trpc创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考