react-stinky について
React Stinkyは、ReactとTypeScriptのコードを対象とするコードスメル確認スキルです。保守性、正確性、アクセシビリティ、型の問題を調べ、深刻度や影響、具体的な修正案を示します。
主な機能
- 01Reactの保守性に関する9領域を確認
- 02状態とeffectの問題を検出
- 03アクセシビリティと描画の正確性を確認
- 04TypeScriptと非同期処理の問題を指摘
- 05フォルダーやリポジトリでファイル間の重複を検査
- 06深刻度、影響、修正案を報告
活用シーン
- 01リファクタリング前のReactコンポーネント確認
- 02effectや状態設計の問題の発見
- 03UIマークアップのアクセシビリティ確認
- 04リポジトリ内の重複したコンポーネントやツールの検出
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ saschb2b/ skills --skill react-stinkyインストール後、Claude Code で/react-stinkyを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
CARD.manifest.jsonCARD.mdCARD.md.sigstoreCARD.svgpermissions.yamlreferences/catalog.mdduplication-pass.mdindex.mdlog.mdrestructure-pass.mdreferences/concepts/composition-layer.mdcontrolled-uncontrolled.mdeffects-model.mdhydration.mdimpossible-states.mdindex.mdone-fact-one-home.mdrender-snapshots.md詳細
React Stinky
React と TypeScript 向けの包括的なコードスメル検出ツールです。コンポーネント、フック、モジュールの読みやすさ、理解しやすさ、変更しやすさを損なうパターンを見つけ、それぞれの問題点を説明し、具体的な修正方法を提案します。検出対象は、プロップと API の設計、状態とデータのフロー、エフェクトとライフサイクル、コンポーネントの構成、レンダリングの正確性、アクセシビリティ、非同期処理とエラー処理、TypeScript の規律にわたります。検出シグナル、修正方法、例外、出典を含む完全なカタログは catalog.md にあります。スキャンを実行する前にお読みください。
隣接する 2 つの懸念事項は重複を避けるため、関連する別のスキルに委ねています。メモ化(useMemo、useCallback、React.memo)は react-compiler に、色のリテラルは theme-colors に委ねています。これらがインストールされていない場合は、該当事項を 1 行で記録して先に進んでください。日常的な React の保守性に関するその他のすべてが対象です。
検出対象
9 つの柱があります。それぞれのカテゴリーと、検出シグナルおよび情報源については、catalog.md をご覧ください。検出結果の背景となるモデル(制御状態と非制御状態、レンダーのスナップショット、同期としてのエフェクト、1 つの事実に 1 つの置き場所、不可能な状態、コンポジションレイヤー、ハイドレーション)は、references/concepts/ に個別の関連概念としてまとめられています。検出結果の名称だけでなく、その基盤となるモデルの説明が必要な場合に、該当する概念をお読みください。
- コンポーネント API と props(基盤となる柱で、18 のカテゴリー)。コンポーネントと props の命名、boolean とコールバックの規約、boolean フラグより文字列のユニオン型によるバリアントを優先すること、判別可能なユニオン、制御状態と非制御状態、children とスロットによるコンポジション、レンダープロップ、ジェネリクス、HTML の拡張、refs、スタイリング API、アクセシビリティ用 props、サーバーコンポーネントの境界、JSDoc。
- 状態とデータフロー。
useStateで保持される導出可能な値、props の state へのコピー、1 つの事実に対する 2 つの真実のソース、props のバケツリレー、タイマーや非同期処理の継続部分における古いクロージャ、useStateとuseRefの役割の混同、巨大なコンテキストと不安定なプロバイダー値。 - エフェクトとライフサイクル。 導出データを計算する、イベントハンドラーを置き換える、または親にデータを渡すエフェクト。クリーンアップのないフェッチ、サブスクリプション、タイマー(競合状態やリークの原因)。エフェクトが読み取る内容と一致しない依存配列。
keyではなくエフェクトで行う state のリセット。 - コンポーネント構造とフック。 フェッチ、ロジック、プレゼンテーションを一度に担う巨大なコンポーネント(および巨大なフック)。データアクセスやドメインルールが属する上位レイヤーに結合されたビューは、動作を表す名前の props として渡す必要があります(モックなしで Storybook にレンダリングできるかどうかが判断基準です)。別のコンポーネント内で定義されたコンポーネント(レンダーのたびに再マウントされます)。カスタムフックに切り出すべきステートフルなロジック。条件付きフック。フックやユーティリティの位置引数の肥大化。
- レンダリングの正しさ。 並べ替えや編集が行われるリストで配列インデックスを
keyとして使うこと。state や props の直接変更。不純なレンダー(乱数や時刻の読み取り、レンダー中のsetState、ハイドレーションに安全でないブラウザー情報の読み取り)。&&によって漏れ出す余分な0。ネストされた三項演算子。コピー&ペーストされた JSX ブロック。 - マークアップのアクセシビリティ。
role、tabIndex、キーボードハンドラーがない、非インタラクティブな要素へのonClick。セマンティック要素を使うべき箇所に div が多用されていること。関連付けられたラベルのないフォームコントロール。手作りのモーダルやメニューにおける壊れたフォーカス管理。 - 非同期処理、イベント、エラーハンドリング。 エラーを握りつぶしたり、二重送信を許したりする非同期ハンドラー。リスクのあるサブツリーを囲むエラーバウンダリーの欠如。サニタイズされていない HTML や URL の出力先(
dangerouslySetInnerHTML、javascript:href)。 - TypeScript の規律。
any、as any、@ts-ignore。事実と異なるasキャストや非 null アサーション!。曖昧な内部型(object、Function、文字列で型付けされた enum)。型付けされていない catch ブロック。網羅性チェックなしで切り替えられるユニオン型。 - ファイル間の重複(フォルダーおよびリポジトリの範囲のみ)。再利用可能なコンポーネントがあるのに、インラインで再実装されたコンポーネント。複数のファイルにコピーされた同じフックやユーティリティ。2 か所で宣言された型。手法については duplication-pass.md をご覧ください。
スコープモード
依頼に合わせてスコープを選び、その範囲に対して以下のワークフローを実行します。
| モード | トリガー | スキャン対象 |
|---|---|---|
| リポジトリ全体のスイープ | 「コードベースの臭いをチェックして」 | **/*.tsx に加え、フックとモジュールの .ts(use-*.ts、lib/)を Glob で検索します。依頼がない限り、node_modules、ビルド出力、生成コード、*.test.*、*.stories.* はスキップします。共有、コンポーネント、フック、ui ディレクトリとエクスポートされたシンボルを優先します。 |
| フォルダーのスキャン | 1 つ以上のディレクトリが指定された場合 | 同じ対象を、指定されたディレクトリに限定してスキャンします。 |
| ファイルのスキャン | 特定のファイルが指定された場合 | 各ファイルを最後まで読みます。すべてのコンポーネント、フック、プロパティインターフェース、エクスポートされた関数を確認します。 |
| フラグメントの確認 | 関数またはコンポーネントの貼り付け、あるいはシンボルが 1 つ指定された場合 | その箇所だけを確認します。表示されていない部分について、何を前提としたかを明記します。 |
フォルダーおよびリポジトリ全体のスコープでは、単一ファイルのスキャンでは見つけられない DRY に反する問題(別の場所でインラインに再実装されたコンポーネント、複数ファイルにコピー&ペーストされた同じフックや型)を検出するため、ファイル横断の重複チェック(duplication-pass.md)も実行します。単一ファイルおよびフラグメントのスコープではこれを確認できないため、ファイル横断の重複はチェックしていないと伝え、コードが重複していないかのような印象を与えないでください。
対象ごとのワークフロー
- スコープ内のコンポーネント、フック、プロパティインターフェース、モジュールを特定します。
- カタログにあるファイルごとの柱(1〜8)に沿って、それぞれを確認します。
- 報告する前に、該当する「指摘しない」行を確認します。該当する場合は、指摘を抑制します。
- 臭いのレベルを評価します(下記の評価を参照)。
- 場所、コスト、修正前から修正後への変更を含む指摘を出します。
- フォルダーまたはリポジトリ全体のスコープでは、ファイルごとの確認後にファイル横断の重複チェック(柱 9、duplication-pass.md)を実行し、その指摘をまとめます。
- 重複した状態、プロパティのバケツリレー、親へのエフェクトデータ、巨大コンポーネント、密結合したビューといった構造上の問題に関する指摘が集中している場合、または依頼がコンポーネントの再構築、疎結合化、再利用可能化の場合は、restructure-pass.md の依存関係グラフ手法で修正を計画します。すべての関係をラベル付きのエッジとしてマッピングし、コードを提案する前に、1 層のエッジを最小限にして接続し直します。
- 最後に指摘件数の要約を示します。チェックを通過した問題がなければ、臭いは見当たらないと伝えます。
臭いの評価
- 腐敗。 バグ、または正確性、アクセシビリティ、サーバー境界に関する問題です。すぐに修正してください。(コントロールの
aria-labelやキーボードハンドラーの欠落、RSC 境界をまたぐ関数、value={0}を受け付けないvalue || 50、変更された状態配列、条件付きフック、状態にコピーされてずれが生じるプロパティ、{count && ...}で描画される余計な0、ハイドレーションを壊すDate.now()ID、サニタイズされていないdangerouslySetInnerHTML。) - 不快。 バグではありませんが、実際に保守性を損なう問題です。修正してください。(1 つのユニオン型にまとめるべきブール値の乱立、巨大コンポーネント、派生データを計算するエフェクト、保留中またはエラー状態のない非同期保存、複合コンポーネントにすべき設定配列。)
- かすか。 軽微またはスタイル上の問題です。修正は任意です。(カスタムプロパティの単なる
loading、型が緩いRecord<string, unknown>の sx、名前を言い換えているだけの JSDoc。)
指摘しないもの(このスキルの有用性を保つためのガード)
カタログには、各コードスメルに対する例外が記載されています。以下はすべてに共通します。これらを守らないと、このスキルは厄介なものになってしまいます。
- ネイティブ HTML 属性名はそのままにします。ネイティブの
<input>では、disabled、required、checked、openにisを付けたり、onChangeの名前を変更したりしないでください。 - 定着したライブラリの規約はコードスメルではありません。ファイル内ですでに使われているライブラリに合わせてください(MUI の
open、slots、sx、Radix のasChild)。 - データ駆動型で固定レイアウトのコンポーネント(データグリッドなど)では、設定オブジェクトの props が適切です。
- エフェクトは、外部との真の同期(サブスクリプション、React 以外のウィジェット、ブラウザー API)に適したツールです。派生データを計算したり、イベントハンドラーを置き換えたりするエフェクトだけを指摘してください。
- 意図的に初期状態を設定し、名前にもそれが示されている場合(
defaultValue)、props を state にコピーしても問題ありません。後から props が変わったときに state も更新される想定なのに更新されない場合のみ指摘してください。 - 並べ替え、挿入、削除が発生しない静的リストでは、インデックスをキーにしても問題ありません。
- あるコンポーネントの内部に定義したコンポーネントは、小規模でステートレスなレンダーヘルパーなら問題ありません。state を保持する場合、メモ化されている場合、または複雑な場合は指摘してください。
- 型付けされていない境界での
anyは、実用的な場合もあります。unknownを使って絞り込む方法を優先しますが、これを理由にブロックしないでください。 - 実際の問題ごとに指摘は1件にします。コードスメルを解消できる最小限の修正を優先してください。カタログの既定値よりも、一貫したローカル規約を尊重してください。
- メモ化は
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.
対象範囲に問題がない場合は、明確に次のように伝えてください:"Smells fresh. No maintainability smells found in <scope>."
ソース
18種類のコンポーネント API カテゴリー(ピラー1)は、cant-maintain React API デザイン課題セットから抽出されており、それぞれ React、TypeScript、MDN、Next.js、または MUI の情報源に基づいています。残りのピラーでは、同じ保守性の観点を state、エフェクト、構造、レンダリング、アクセシビリティ、非同期処理とエラー処理、型にまで広げており、それぞれの情報源は catalog.md に記載された React、MDN、または TypeScript ハンドブックの公式ドキュメントです。