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: Mapstring, QueryDefaults #mutationDefaults: Mapstring, 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节点需要同时满足以下条件才会报告错误构造的必须是QueryClientnode.callee必须是一个名为QueryClient的标识符必须直接来自tanstack/react-query通过导入检测工具确认该标识符确实从tanstack/react-query导入必须位于变量声明中NewExpression的父节点是VariableDeclarator所在函数是 React 组件或 Hook最近的函数祖先的名字符合 React 组件大写开头或 Hookuse前缀的命名约定所在函数不是 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 负责向上遍历祖先节点找到第一个FunctionDeclaration、FunctionExpression或ArrowFunctionExpression。由此可以推断出规则的完整判定边界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不是Identifierfixer 提前返回只报告错误而不自动修复测试用例 QueryClient with destructuring pattern reports error without autofix 中output: null明确验证了这一点。此类写法仍需手工改为模块级常量或useState形式。从测试用例还能看出规则对「稳定写法」的认定比文档示例更宽泛React.useState、useState、React.useMemo(() new QueryClient(), [])甚至任意名为useAnything的自定义 Hook 包裹的构造都因不满足「组件内直接new QueryClient」的模式而不被报告。从源码结构看这是因为规则只匹配「NewExpression 直接作为 VariableDeclarator 的 init」这一形态被函数调用包裹的构造天然不在监听范围内。在项目中使用该规则安装插件是独立发布的包package.json 中版本为 5.102.8peer 依赖eslint ^8.57.0 || ^9.0.0 || ^10.0.0可选 peer 依赖typescript ^5.6.0 || ^6.0.0 || ^7.0.0npm i -D tanstack/eslint-plugin-query或使用 pnpm / yarn / bunpnpm add -D tanstack/eslint-plugin-query yarn add -D tanstack/eslint-plugin-query bun add -D tanstack/eslint-plugin-queryFlat Configeslint.config.js推荐直接使用插件自带的 preset插件 configs 定义flat/recommended中即包含tanstack/query/stable-query-client: errorimport pluginQuery from tanstack/eslint-plugin-query export default [ ...pluginQuery.configs[flat/recommended], // Any other config... ]如需更严格的约束可以使用flat/recommended-strict在 recommended 基础上额外启用prefer-query-optionsimport 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一个应用只能有一个长期存活的实例。掌握它需要理解三层内容行为层组件/自定义 Hook 内直接new QueryClient()会被报告推荐改写为const [queryClient] useState(() new QueryClient())或提升为模块级常量边界层规则仅对从tanstack/react-query导入的QueryClient生效且豁免非组件函数非use*/非大写命名与 async Server Component实现层触发判定基于 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),仅供参考