news 2026/9/7 5:58:04

TanStack Query stable-query-client 规则详解:确保 QueryClient 实例稳定,避免每次渲染丢失缓存

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
TanStack Query stable-query-client 规则详解:确保 QueryClient 实例稳定,避免每次渲染丢失缓存

TanStack Query stable-query-client 规则详解:确保 QueryClient 实例稳定,避免每次渲染丢失缓存

【免费下载链接】query🤖 Powerful asynchronous state management, server-state utilities and data fetching for the web. TS/JS, React Query, Solid Query, Svelte Query and Vue Query.项目地址: https://gitcode.com/GitHub_Trending/qu/query

本文围绕 TanStack Query 官方 ESLint 插件中的stable-query-client规则展开,讲解它要解决的真实问题——QueryClient 在组件渲染过程中被反复重建导致的缓存丢失;给出官方文档中的正确与错误写法对照,并结合插件源码 stable-query-client.rule.ts、导入检测工具 detect-react-query-imports.ts 以及测试用例 stable-query-client.test.ts,完整还原该规则的触发条件、豁免逻辑与自动修复行为,帮助你在项目中正确配置并理解这条推荐级规则。

为什么 QueryClient 必须稳定

规则文档的核心论点是:QueryClient 内部持有 QueryCache,因此整个应用生命周期内只应创建一个实例,而不能在每次渲染时都新建一个。这一论断可以直接在核心包源码中得到印证。QueryClient 类定义 显示:

export class QueryClient { #queryCache: QueryCache #mutationCache: MutationCache #defaultOptions: DefaultOptions #queryDefaults: Map<string, QueryDefaults> #mutationDefaults: Map<string, MutationDefaults> #mountCount: number // ... constructor(config: QueryClientConfig = {}) { this.#queryCache = config.queryCache || new QueryCache() this.#mutationCache = config.mutationCache || new MutationCache() // ... } }

如果不在配置中显式传入queryCache,每次new QueryClient()都会构造一个全新的空 QueryCache(以及空的 MutationCache)。也就是说,当你在组件函数体里直接new QueryClient()时,组件每重渲染一次,之前所有的查询数据、请求状态就会随着旧实例一起被丢弃,界面表现为数据反复闪烁、请求重复发出。这正是stable-query-client规则要拦截的「problem」类错误——插件元信息中将其标记为type: 'problem',描述为 "Makes sure that QueryClient is stable",并且在推荐配置中默认以error级别启用(见 插件入口 index.ts)。

规则属性与基本行为

stable-query-client规则的两个关键属性:

属性说明
推荐级别属于recommended配置,默认以error级别开启
可自动修复是(fixable: 'code'),可自动改写为React.useState写法
规则选项无(schema: []defaultOptions: []),开启即生效

报告的错误信息(规则源码 messages.unstable)为:

QueryClient is not stable. It should be either extracted from the component or wrapped in React.useState.

即修复方向只有两条:把QueryClient提取到组件外部(模块作用域),或者用React.useState包裹构造过程。

错误示例

下面的写法会触发该规则:new QueryClient()位于组件函数体内部,每次渲染都会执行,生成新的实例。

/* eslint "@tanstack/query/stable-query-client": "error" */ function App() { const queryClient = new QueryClient() return ( <QueryClientProvider client={queryClient}> <Home /> </QueryClientProvider> ) }

正确示例

官方文档给出了三种合规写法,分别对应「useState 惰性初始化」「模块级单例」「服务端异步组件」三个场景:

// 方式一:useState 惰性初始化(规则自动修复后的标准形态) function App() { const [queryClient] = useState(() => new QueryClient()) return ( <QueryClientProvider client={queryClient}> <Home /> </QueryClientProvider> ) }
// 方式二:模块作用域创建,应用生命周期内唯一实例 const queryClient = new QueryClient() function App() { return ( <QueryClientProvider client={queryClient}> <Home /> </QueryClientProvider> ) }
// 方式三(例外情况):异步 Server Component 内创建 async function App() { const queryClient = new QueryClient() await queryClient.query(options) }

文档特别强调的例外是:在异步 Server Component中允许创建新的 QueryClient,因为该 async 函数在服务端只会被调用一次,不存在「每次渲染重建」的问题。这一点在规则实现中被显式处理(下文详述),并有对应测试用例覆盖。

规则实现解析:什么情况下会触发报告

规则的完整实现位于 stable-query-client.rule.ts。它监听 AST 中的每一个NewExpression节点,需要同时满足以下条件才会报告错误:

  1. 构造的必须是QueryClientnode.callee必须是一个名为QueryClient的标识符;
  2. 必须直接来自@tanstack/react-query:通过导入检测工具确认该标识符确实从@tanstack/react-query导入;
  3. 必须位于变量声明中NewExpression的父节点是VariableDeclarator
  4. 所在函数是 React 组件或 Hook:最近的函数祖先的名字符合 React 组件(大写开头)或 Hook(use前缀)的命名约定;
  5. 所在函数不是 async 函数:async 函数被视为 Server Component,予以豁免。

只针对 @tanstack/react-query 的导入

规则通过 detectTanstackQueryImports 高阶函数增强。该工具在文件解析初期收集所有ImportDeclaration,只保留满足「模块名以@tanstack/开头、以-query结尾」的导入(检测逻辑);随后规则用isSpecificTanstackQueryImport(node.callee, '@tanstack/react-query')精确比对来源模块。

这一设计的实际效果在测试用例中得到清晰体现(测试文件 valid 部分):

  • QueryClient"other-library"导入 —— 不报告(不是 TanStack Query 生态的标识符);
  • QueryClient@tanstack/solid-query导入 —— 不报告(该规则只对 React 版负责,因为规则针对的是 React 渲染循环带来的实例不稳定问题)。

组件/Hook 命名约定与 async 豁免

判断「所在函数是否是 React 组件或 Hook」依赖 ASTUtils.isValidReactComponentOrHookName,其实现就是一个正则:/^(use|[A-Z])/,即函数名以use开头或大写字母开头。而 getFunctionAncestor 负责向上遍历祖先节点,找到第一个FunctionDeclarationFunctionExpressionArrowFunctionExpression

由此可以推断出规则的完整判定边界:

  • new QueryClient()写在一个普通函数(如function someFn())里 ——不报告,因为它不是组件/Hook,不处于渲染循环中;
  • 写在模块顶层 ——不报告,没有函数祖先;
  • 写在async函数里 ——不报告,规则源码 中isReactServerComponent = fnAncestor?.async === true直接豁免,这正是文档中「异步 Server Component 例外」的实现依据。

自动修复:改写为 React.useState

当违规代码是const xxx = new QueryClient(...)形式(变量名为普通标识符)时,规则提供代码级自动修复(fixer 实现):将整个变量声明替换为[xxx] = React.useState(() => new QueryClient(...)),并完整保留原有的构造参数。测试用例验证了修复的四个关键细节:

// 1. 组件内的直接构造 —— 报告并修复 function Component() { const queryClient = new QueryClient() } // 自动修复为: // const [queryClient] = React.useState(() => new QueryClient()) // 2. 自定义 Hook 内同样适用 function useHook() { const queryClient = new QueryClient() } // 自动修复为: // const [queryClient] = React.useState(() => new QueryClient()) // 3. 构造参数原样保留 const queryClient = new QueryClient({ defaultOptions: { /* ... */ } }) // 修复为: // const [queryClient] = React.useState(() => new QueryClient({ defaultOptions: { /* ... */ } })) // 4. 变量名保持原样 const customName = new QueryClient() // 修复为: // const [customName] = React.useState(() => new QueryClient())

需要注意的是修复能力的边界:当变量声明是解构形式(如const { defaultOptions } = new QueryClient())时,由于parent.id.type不是Identifier,fixer 提前返回,只报告错误而不自动修复(测试用例 "QueryClient with destructuring pattern reports error without autofix" 中output: null明确验证了这一点)。此类写法仍需手工改为模块级常量或useState形式。

从测试用例还能看出,规则对「稳定写法」的认定比文档示例更宽泛:React.useStateuseStateReact.useMemo(() => new QueryClient(), []),甚至任意名为useAnything的自定义 Hook 包裹的构造,都因不满足「组件内直接new QueryClient」的模式而不被报告。从源码结构看,这是因为规则只匹配「NewExpression 直接作为 VariableDeclarator 的 init」这一形态,被函数调用包裹的构造天然不在监听范围内。

在项目中使用该规则

安装

插件是独立发布的包(package.json 中版本为 5.102.8,peer 依赖eslint ^8.57.0 || ^9.0.0 || ^10.0.0,可选 peer 依赖typescript ^5.6.0 || ^6.0.0 || ^7.0.0):

npm i -D @tanstack/eslint-plugin-query

或使用 pnpm / yarn / bun:

pnpm add -D @tanstack/eslint-plugin-query yarn add -D @tanstack/eslint-plugin-query bun add -D @tanstack/eslint-plugin-query

Flat Config(eslint.config.js)

推荐直接使用插件自带的 preset(插件 configs 定义),flat/recommended中即包含'@tanstack/query/stable-query-client': 'error'

import pluginQuery from '@tanstack/eslint-plugin-query' export default [ ...pluginQuery.configs['flat/recommended'], // Any other config... ]

如需更严格的约束,可以使用flat/recommended-strict(在 recommended 基础上额外启用prefer-query-options):

import pluginQuery from '@tanstack/eslint-plugin-query' export default [ ...pluginQuery.configs['flat/recommended-strict'], // Any other config... ]

当然也可以只加载本规则,按需配置级别:

import pluginQuery from '@tanstack/eslint-plugin-query' export default [ { plugins: { '@tanstack/query': pluginQuery, }, rules: { '@tanstack/query/stable-query-client': 'error', }, }, // Any other config... ]

Legacy Config(.eslintrc)

{ "extends": ["plugin:@tanstack/query/recommended"] }

或自定义:

{ "plugins": ["@tanstack/query"], "rules": { "@tanstack/query/stable-query-client": "error" } }

完整的插件配置说明可参见 ESLint Plugin 文档,本规则在规则列表中的位置见该文档的 Rules 一节;其他规则文档如 exhaustive-deps、no-unstable-deps 可作为配套阅读。

小结

stable-query-client是 TanStack Query 官方 ESLint 插件中一条推荐开启(error级别)且可自动修复的规则,它针对的是一条朴素但高价值的约束:QueryClient 持有 QueryCache,一个应用只能有一个长期存活的实例。掌握它需要理解三层内容:

  1. 行为层:组件/自定义 Hook 内直接new QueryClient()会被报告,推荐改写为const [queryClient] = useState(() => new QueryClient())或提升为模块级常量;
  2. 边界层:规则仅对从@tanstack/react-query导入的QueryClient生效,且豁免非组件函数(非use*/非大写命名)与 async Server Component;
  3. 实现层:触发判定基于 NewExpression 与变量声明的直接父子关系,自动修复保留变量名与构造参数,解构声明形式只报错不修复。

理解了这些细节,你不仅能正确使用这条规则,也能在面对类似「某条 ESLint 规则为什么没报/误报」的问题时,像本文一样从规则源码、导入检测工具与 RuleTester 用例三个层面快速定位其行为边界。

【免费下载链接】query🤖 Powerful asynchronous state management, server-state utilities and data fetching for the web. TS/JS, React Query, Solid Query, Svelte Query and Vue Query.项目地址: https://gitcode.com/GitHub_Trending/qu/query

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 5:58:00

OpenCV人脸识别实战:从方案选型到参数调优全解析

简介&#xff1a;这是一份面向 OpenCV 入门开发者的 Java 人脸识别示例工程&#xff0c;适合想学习 Haar 级联检测、人脸特征提取与识别流程&#xff0c;或需要在 Eclipse 环境中快速搭建 OpenCV 项目的读者。资源包为一个 485KB 的 zip 压缩包&#xff0c;共含 25 个文件&…

作者头像 李华
网站建设 2026/9/7 5:55:44

OFDM系统PAPR抑制实战:PTS算法三种分割方式的MATLAB实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 5:54:35

YOLOv8+PyQt5路面缺陷检测系统设计:从模型训练到部署全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 5:52:54

FunASR 离线语音转写:Windows 本地部署 64 路并发实操手册

FunASR 离线语音转写&#xff1a;Windows 本地部署 64 路并发实操手册 【免费下载链接】FunASR Open-source speech recognition toolkit for training, inference, streaming ASR, VAD, punctuation, speaker diarization pipelines, and OpenAI-compatible/MCP serving. 项…

作者头像 李华