首页
/ TanStack Query ESLint 规则 mutation-property-order:useMutation 中类型推断敏感属性的顺序规范与实现剖析

TanStack Query ESLint 规则 mutation-property-order:useMutation 中类型推断敏感属性的顺序规范与实现剖析

2026-09-05 15:47:38作者:柏廷章Berta

本篇聚焦 TanStack Query 的 ESLint 插件规则 mutation-property-order(文档:docs/eslint/mutation-property-order.md)。它强制 useMutation() 配置对象中 onMutateonErroronSettled 三个属性按特定顺序书写,以保障 TypeScript 对回调参数的类型推断正确。读完后你将理解:为什么对象属性书写顺序会影响类型推断、该规则的检查范围与豁免范围、它在插件源码中的检测与自动修复链路,以及如何在项目中启用这条可自动修复(Fixable)的推荐规则。

规则要解决的问题:属性顺序为何影响类型推断

useMutation() 中,onErroronSettled 回调会收到 onMutate 的返回值(通常作为恢复快照的 context / onMutateResult 参数)作为入参。文档明确指出:由于类型推断(type inference)的原因,传入对象的属性顺序是有意义的,其中仅以下属性对顺序敏感:

  • useMutation()

正确的属性顺序为:

  1. onMutate
  2. onError
  3. onSettled

其余所有属性(如 mutationFnonSuccessretrygcTime 等)不参与顺序约束,因为它们不依赖类型推断链路。

问题出在 onMutate 的返回类型是 TContext 的推断来源。TypeScript 对对象字面量做上下文类型推断时,属性按书写顺序参与泛型推断;当 onError / onSettled 写在 onMutate 之前时,此时 onMutate 尚未被“看见”,其返回类型还未纳入推断,这两个回调的 context 参数就难以被推断为 onMutate 返回的具体类型(例如 { backup: string }),从而退化为宽泛的 TContext | undefined 类型,逼迫开发者手写类型标注,甚至掩盖类型错误。文档给出的反例正是这种场景:onSettledonError 抢在 onMutate 前面,规则即会报告 Invalid order of properties for useMutation. 并给出自动重排修复。

规则元信息

从规则源码 mutation-property-order.rule.ts 可以确认其元信息,与文档中的 Attributes 小节(✅ Recommended、🔧 Fixable)一致:

元信息 取值 说明
规则名 mutation-property-order 配置时写作 @tanstack/query/mutation-property-order
type problem 归类为正确性问题,而非风格问题
recommended error 在插件的推荐配置中以 error 级别开启
fixable code 提供自动修复,直接重排属性
hasSuggestions true 元信息中同时声明了建议能力
schema [] 规则不接受任何选项,属于纯约束规则

规则细节:错误与正确的代码示例

文档给出的错误示例(onSettledonMutateonError 乱序):

/* eslint "@tanstack/query/mutation-property-order": "warn" */
import { useMutation } from '@tanstack/react-query'

const mutation = useMutation({
  mutationFn: () => Promise.resolve('success'),
  onSettled: () => {
    results.push('onSettled-promise')
    return Promise.resolve('also-ignored') // Promise<string> (should be ignored)
  },
  onMutate: async () => {
    results.push('onMutate-async')
    await sleep(1)
    return { backup: 'async-data' }
  },
  onError: async () => {
    results.push('onError-async-start')
    await sleep(1)
    results.push('onError-async-end')
  },
})

对应的正确写法是把三个推断敏感属性重排为 onMutateonErroronSettled

/* eslint "@tanstack/query/mutation-property-order": "warn" */
import { useMutation } from '@tanstack/react-query'

const mutation = useMutation({
  mutationFn: () => Promise.resolve('success'),
  onMutate: async () => {
    results.push('onMutate-async')
    await sleep(1)
    return { backup: 'async-data' }
  },
  onError: async () => {
    results.push('onError-async-start')
    await sleep(1)
    results.push('onError-async-end')
  },
  onSettled: () => {
    results.push('onSettled-promise')
    return Promise.resolve('also-ignored') // Promise<string> (should be ignored)
  },
})

对比两个示例可以看出规则的约束边界:mutationFn 写在最前并不违规,违规点仅在 onSettled 出现在 onMutate 之前。

常量定义:检查范围与排序规则

规则的检查范围由 constants.ts 精确声明:

export const mutationFunctions = ['useMutation'] as const
export const checkedProperties = ['onMutate', 'onError', 'onSettled'] as const
export const sortRules = [[['onMutate'], ['onError', 'onSettled']]] as const

这里有一个文档未展开、但从常量可以确认的关键细节sortRules 将属性划分为两个分组——onMutate 单独一组,onErroronSettled 同组。也就是说规则真正约束的是「onMutate 必须排在 onError / onSettled 之前」,而 onErroronSettled 二者之间的相对顺序是不检查的。这一点在测试文件 mutation-property-order.rule.test.ts 中被显式验证:生成非法排列时会跳过「onErroronSettled 相邻且 onMutate 不存在」的排列,并在构造期望值时注明 "since we ignore the relative order of 'onError' and 'onSettled'"。文档列出的 onMutate → onError → onSettled 应视为官方推荐的书写顺序,而非三者全序强制。

源码级实现:从导入检测到自动修复

通用工厂:createPropertyOrderRule

该规则并非独立实现,而是由通用工厂 create-property-order-rule.ts 生成——同一工厂还服务于姊妹规则 infinite-query-property-order(后者约束 queryFn 先于 getPreviousPageParam / getNextPageParam,见 constants.ts)。工厂的核心检测逻辑:

  1. 监听 CallExpression 节点,callee 必须是标识符且命中目标函数集合(本规则仅 useMutation);
  2. 第一个参数必须是 ObjectExpression,否则跳过(例如 useMutation(fn, options) 的函数式签名不会被检查);
  3. 属性少于 2 个直接跳过(无排序空间);
  4. 将对象属性平铺为 { name, property } 列表:key 为标识符的属性取真实名字,其余(展开元素、计算键等)统一命名为占位符 _property_${index},即进入「顺序不敏感」阵营;
  5. 调用排序工具得到期望顺序,若与原顺序一致则不报告,否则以 invalidOrder 报告并附带 fixer。

排序算法:稳定的分组排序

排序由 sort-data-by-order.ts 完成。它把 orderRules 展开为有序分组序列([onMutate 组, onError/onSettled 组]),按「所属分组下标」对受约束属性做稳定排序,不命中任何分组的属性(mutationFngcTime、各类 spread)保持原位不动;若排序结果与输入无差异则返回 null(规则据此不报告)。稳定排序保证了同组属性之间的原有相对顺序不被打乱。

导入检测:只对来自 TanStack Query 的 useMutation 生效

规则外层包裹了 detect-react-query-imports.ts 中的 detectTanstackQueryImports:它先扫描 ImportDeclaration,只登记来源模块以 @tanstack/ 开头且以 -query 结尾(如 @tanstack/react-query@tanstack/solid-query)的具名导入,规则只对解析到这些导入的 useMutation 标识符触发检查。从源码结构看,本地自行定义或从非 TanStack 包导入的同名函数不会被误报。

自动修复:重排属性且保留原有间隔文本

fixer(位于工厂的 fix(fixer) 回调中)将排序后的每个属性按新顺序拼接,并用 sourceCode.getText().slice(...) 取回原对象中相邻属性之间的原始文本(空白、换行、注释)填充到新序列的对应间隙,最后以 fixer.replaceTextRange 一次性替换整个属性区间。这意味着自动修复不仅重排属性,也完整保留属性之间的注释与格式——测试矩阵 mutation-property-order.rule.test.ts 中每个非法用例都断言了 output 精确等于重排后的代码,验证了这一行为。

测试矩阵:组合化验证与回归用例

测试文件用 combinate 对「目标函数 × 属性排列」做笛卡尔积,并额外把顺序不敏感属性(gcTime...objectExpressionSpread...callExpressionSpread...memberCallExpressionSpread)以插值方式混入,验证:

  • 三个受检属性按合法子序列出现时一律通过;
  • 任意使 onMutate 落在 onError / onSettled 之后的排列(且非「onError/onSettled 相邻而无 onMutate」的豁免情形)都会报告 invalidOrder,且自动修复输出即为合法排列;
  • 回归用例覆盖 ...mutationOptions({...}) 这类调用表达式展开onMutate/onError/onSettled 混排的场景——spread 被视为不透明占位符,既不会触发误报,也不会被 fixer 移动。

在项目中的接入方式

规则由 rules.ts 统一注册,发布包为 @tanstack/eslint-plugin-querypackage.json 中 peer 依赖为 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0,TypeScript 为可选 peer ^5.6.0 || ^6.0.0 || ^7.0.0),因此同时兼容传统与 flat 配置。仓库内的可运行示例 examples/react/eslint-plugin-demo/eslint.config.js 展示了 flat config 下的推荐接入:

import pluginQuery from '@tanstack/eslint-plugin-query'
import tseslint from 'typescript-eslint'

export default [
  ...tseslint.configs.recommended,
  ...pluginQuery.configs['flat/recommended-strict'],
  {
    files: ['src/**/*.ts', 'src/**/*.tsx'],
    rules: {
      '@tanstack/query/exhaustive-deps': [
        'error',
        {
          allowlist: {
            variables: ['api'],
            types: ['AnalyticsClient'],
          },
        },
      ],
    },
  },
]

引入 flat/recommended-strict 配置后,mutation-property-order 作为推荐规则默认生效(规则元信息中 recommended: 'error')。如需手动控制级别,可直接按文档示例的注释语法在规则中单独声明 @tanstack/query/mutation-property-order。该规则无选项(schema 为空数组),无需任何参数配置;配合 --fix 即可一键重排。

适用边界小结

  • 仅检查 useMutation 的单参数对象字面量写法;useMutation(fn, options) 函数签名不在检查范围;
  • 仅约束 onMutateonError / onSettled 的前后关系,onErroronSettled 之间相对顺序自由,其余属性与位置无关;
  • 展开语法(对象、调用表达式、成员调用)被当作顺序不敏感占位符,不影响判定也不参与重排;
  • 只对从 @tanstack/*-query 包导入的 useMutation 生效,避免误伤同名本地函数。

参考文件

登录后查看全文
热门项目推荐
相关项目推荐