mcpField
R
react-stinky

react-stinky

github.com/saschb2b测试与质量22144 次安装2 周前更新
代码仓库

关于 react-stinky

React Stinky 是用于审查 React 和 TypeScript 代码异味的 skill,检查可维护性、正确性、无障碍和类型问题,并给出严重程度、影响及具体修复建议。

主要功能

  1. 01检查九类 React 可维护性问题
  2. 02发现状态与 effect 问题
  3. 03审查无障碍和渲染正确性
  4. 04标记 TypeScript 与异步处理问题
  5. 05目录或仓库范围内检查跨文件重复
  6. 06报告严重程度、代价和修复建议

适用场景

  1. 01重构前审查 React 组件
  2. 02发现 effect 与状态设计问题
  3. 03检查 UI 标记中的无障碍问题
  4. 04扫描仓库中的重复组件或工具

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/saschb2b/skills --skill react-stinky

安装后在 Claude Code 中输入/react-stinky确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

CARD.manifest.json
CARD.md
CARD.md.sigstore
CARD.svg
permissions.yaml
references/
catalog.md
duplication-pass.md
index.md
log.md
restructure-pass.md
references/concepts/
composition-layer.md
controlled-uncontrolled.md
effects-model.md
hydration.md
impossible-states.md
index.md
one-fact-one-home.md
render-snapshots.md

详细介绍

React Stinky

一款面向 React 和 TypeScript 的全面代码异味检测工具。它会找出让组件、Hook 或模块难以阅读、推理和修改的模式,解释每种模式的代价,并提出具体的修复方案。检测范围涵盖属性和 API 设计、状态与数据流、Effect 和生命周期、组件结构、渲染正确性、无障碍性、异步与错误处理,以及 TypeScript 规范。完整目录包含检测信号、修复方法、例外情况和来源,见 catalog.md;运行扫描前请先阅读。

它会将两个相邻领域交由相关技能处理,以避免重复:将记忆化(useMemo、useCallback、React.memo)交由 react-compiler,将颜色字面量交由 theme-colors。如果未安装这些技能,请用一行注明发现的问题,然后继续。除此之外,日常 React 可维护性的所有方面都在涵盖范围内。

它会检测什么

九大支柱。每类下的类别及其检测信号和来源见 catalog.md。这些发现所依据的背景模型(受控与非受控、渲染快照、将副作用视为同步、一个事实只有一个归属、不可实现的状态、组合层、hydration)是 references/concepts/ 下单独链接的概念;当某项发现需要解释其底层模型,而不只是点出名称时,再阅读对应概念。

  1. 组件 API 和 props(基础,共 18 类)。组件和 prop 的命名、布尔值和回调约定、优先使用字符串联合变体而非布尔标志、判别联合、受控与非受控状态、children 和插槽组合、渲染 prop、泛型、扩展 HTML、refs、样式 API、无障碍 props、服务端组件边界、JSDoc。
  2. 状态和数据流。 将可推导值存入 useState、将 props 复制到状态中、同一事实存在两个数据源、prop 层层传递、定时器和异步后续操作中的过期闭包、混淆 useState/useRef 的用途、臃肿的 context 和不稳定的 provider 值。
  3. 副作用和生命周期。 用副作用计算派生数据、替代事件处理程序或将数据向上推送给父组件;fetch、订阅和定时器没有清理(竞态和泄漏);依赖数组与副作用读取的内容不匹配;用副作用而不是 key 重置状态。
  4. 组件结构和 hooks。 同时负责获取数据、处理逻辑和呈现的臃肿组件(以及臃肿 hooks);与环境耦合的视图,其中的数据访问和领域规则本应属于更上层,并应作为以行为命名的 props 传入(判断标准是:能否在没有 mock 的情况下于 Storybook 中渲染);在另一个组件内定义组件(每次渲染都会重新挂载);适合提取为自定义 hook 的有状态逻辑;条件式调用 hooks;hook 或工具函数的位置参数泛滥。
  5. 渲染正确性。 对会重新排序或编辑的列表使用数组索引作为 key;直接修改状态或 props;非纯渲染(随机数和读取时钟、在渲染期间调用 setState、不利于 hydration 的浏览器读取);被 && 意外渲染出来的 0;嵌套三元表达式;复制粘贴的 JSX 代码块。
  6. 标记中的无障碍性。 在非交互元素上使用 onClick,却没有 role、tabIndex 或键盘处理程序;应使用语义化元素却堆叠 div;表单控件没有关联的标签;手动实现的模态框和菜单中的焦点管理有误。
  7. 异步、事件和错误处理。 吞掉错误或允许重复提交的异步处理程序;有风险的子树周围缺少错误边界;未清理的 HTML 和 URL 接收点(dangerouslySetInnerHTML、javascript: href)。
  8. TypeScript 规范。 any、as any 和 @ts-ignore;具有误导性的 as 类型断言和非空断言 !;宽泛的内部类型(object、Function、用字符串表示的枚举);未标注类型的 catch 块;切换联合类型时未检查穷尽性。
  9. 跨文件重复(仅限文件夹和仓库范围)。已有可复用组件,却在其他位置内联重写;同一个 hook 或工具函数在多个文件中重复复制;在两个位置声明同一个类型。方法见 duplication-pass.md。

检查范围模式

根据请求匹配检查范围,然后对其运行以下工作流。

模式触发条件扫描内容
仓库全面检查“检查代码库是否有异味”使用 Glob 扫描 **/*.tsx,以及 hook 和模块 .ts 文件(use-*.ts、lib/)。除非另有要求,否则跳过 node_modules、构建输出、生成代码、*.test.*、*.stories.*。优先检查共享目录、组件目录、hook 目录和 ui 目录,以及导出的符号。
文件夹扫描指定一个或多个目录同上,但范围限定为这些目录。
文件扫描指定具体文件完整阅读每个文件。检查每个组件、hook、prop 接口和导出函数。
片段嗅查粘贴的函数或组件,或指定的一个符号只检查该部分。说明对任何未展示内容所作的假设。

文件夹和仓库全面检查范围还要执行跨文件重复检查 (duplication-pass.md),以发现单文件扫描无法发现的 DRY 异味:在其他位置重新内联实现的组件,或跨文件复制粘贴的同一 hook 或类型。单文件和片段范围无法检查这一点,因此应说明未检查跨文件重复,而不能暗示代码没有重复。

每个目标的工作流

  1. 找出范围内的组件、hook、prop 接口和模块。
  2. 根据目录中针对每个文件的支柱(1 到 8)逐一检查。
  3. 报告前先应用对应的“不要标记”条目。如果适用,则不报告该问题。
  4. 为异味评级(见下文)。
  5. 给出问题发现,包含位置、代价,以及从现状到修复方案的对比。
  6. 在文件夹或仓库范围内,完成逐文件检查后,执行跨文件重复检查(支柱 9,duplication-pass.md),并将其发现纳入结果。
  7. 如果问题集中在结构性异味(重复状态、prop 逐层传递、通过 effect 将数据传给父组件、上帝组件、视图耦合),或请求是重构、解耦或让组件可复用,则使用 restructure-pass.md 中的依赖图方法规划修复:将每种关系映射为带标签的边,然后重新连接,使单层边尽可能少,再提出代码方案。
  8. 最后给出问题数量摘要。如果没有问题通过筛查,则说明代码状态良好。

异味评级

  • 腐臭。 存在 bug,或正确性、可访问性或服务端边界方面的问题。立即修复。(控件缺少 aria-label 或键盘处理器、跨越 RSC 边界的函数、value || 50 导致 value={0} 被忽略、状态数组被修改、条件式 hook、复制到状态后会不一致的 props、{count && ...} 渲染出多余的 0、会导致 hydration 出错的 Date.now() ID、未经清理的 dangerouslySetInnerHTML。)
  • 怪味。 确实拖累可维护性,但不是 bug。应该修复。(本应改用一个联合类型的布尔值泛滥、上帝组件、用于计算派生数据的 effect、没有 pending 或错误状态的异步保存、本应改用复合组件的配置数组。)
  • 淡味。 轻微或仅涉及风格。可选修复。(自定义 prop 仅使用 loading、宽泛的 Record<string, unknown> sx 类型、只是重复名称的 JSDoc。)

不要标记(让这项检查保持实用的守则)

目录为每种代码异味都列出了一条例外说明。这些说明适用于所有代码异味。请遵守,否则这项技能就会变成一种麻烦。

  • 原生 HTML 属性名称保持原样。不要给 disabled、required、checked、open 添加 is 前缀,也不要重命名原生 <input> 上的 onChange。
  • 已确立的库惯例不属于代码异味。遵循文件中已有的库惯例(MUI 的 open、slots、sx;Radix 的 asChild)。
  • 对于数据驱动、布局固定的组件(例如数据网格),使用配置对象属性是正确的。
  • 对真正的外部同步(订阅、非 React 小部件、浏览器 API),使用 effect 是正确的做法。只有在 effect 用来计算派生数据或替代事件处理程序时才标记。
  • 当你有意为状态设置初始值,并且名称能体现这一点(defaultValue)时,将属性复制到状态中没有问题。只有在预期属性后续变化会更新状态时才标记。
  • 对于永不重新排序、插入或删除的静态列表,使用索引作为键没有问题。
  • 在另一个组件内部定义组件,用作小型、无状态的渲染辅助函数没有问题。如果它持有状态、经过 memoization 或并非小组件,则应标记。
  • 在确实没有类型的边界处使用 any 有时是权宜之计。优先使用 unknown 并进行缩窄,但不要因此阻止合并。
  • 每个实际问题只报告一次。优先采用能消除代码异味的最小改动。与目录默认规范相比,应尊重一致的本地惯例。
  • 将 memoization 交由 react-compiler 处理,将颜色字面量交由 theme-colors 处理,避免重复处理。

输出格式

React Stinky report, <scope>

src/components/SeedRow.tsx
  [Rancid] clickable-nonsemantic (a11y markup), line 297
    Smell: a <Stack> (renders a div) has onClick but no role, tabIndex, or keyboard handler.
    Cost: keyboard and screen-reader users cannot trigger it; it is invisible to assistive tech.
    Fix: render a real control (component="button" or an IconButton), or add
      role="button" tabIndex={0} and an onKeyDown for Enter and Space.
    Source: MDN button role (https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/button_role)

  [Funky] effect-for-derived (state and effects), line 40
    Smell: a useEffect plus setState computes `fullName` from `first` and `last`.
    Cost: an extra render and a state value that can drift from its inputs.
    Fix: compute during render, `const fullName = `${first} ${last}``. Delete the effect and the state.
    Source: React, You Might Not Need an Effect (https://react.dev/learn/you-might-not-need-an-effect)

Summary: 1 rancid, 1 funky across 1 file.

如果范围内没有问题,请直接说明:“代码异味已清除。在 <scope> 中未发现可维护性问题。”

来源

18 个组件 API 类别(支柱 1)提炼自 cant-maintain React API 设计挑战集,每个类别都可追溯至 React、TypeScript、MDN、Next.js 或 MUI 的来源。其余支柱将同一可维护性视角扩展到状态、effect、结构、渲染、无障碍、异步与错误处理以及类型,每项均取材自 catalog.md 中的 React 官方文档、MDN 或 TypeScript 手册。

评论

支持 Markdown