Ein Code Smell ist das React-Äquivalent zu Milch, die übers Wochenende gekippt ist: Sie gießt sich noch, aber irgendwas stimmt nicht. react-stinky ist ein Agent Skill, der durch deine komplette Komponente, deinen Hook oder dein Modul geht, die Kosten jedes Smells benennt, einen konkreten Fix mit Quelle vorschlägt und (der Teil, der ihn brauchbar macht) weiß, wann er den Mund halten soll. Hier ist, wonach er schnüffelt, wie er den Mief bewertet und warum die Liste der Dinge, die er nicht meldet, wichtiger ist als die Liste der Dinge, die er meldet.
HABEN SIE ES SATT, eine Komponente zu öffnen und vom Mief umgehauen zu werden? Hat Ihr <Stack> ein onClick, das kein Tastaturnutzer je erreicht? Vermehren sich Ihre Boolean-Props wie die Karnickel, isSmall neben isLarge neben isError neben isLoading, bis keiner mehr weiß, welche Kombination überhaupt erlaubt ist? Leute, das kennen wir ALLE. Am Freitag ging der Code sauber raus. Am Montag STINKT er. Aber was, wenn es einen Weg gäbe, den Mief aufzuspüren, bevor es Ihr Reviewer tut? Hier kommt react-stinky, der Code-Smell-Detektor, der durch Ihre ganze Komponente läuft, sich die Nase zuhält und Ihnen genau sagt, was da gestorben ist und wie man es beerdigt. Er schneidet. Er hackt. Er zitiert die React-Docs. Aber Moment, es kommt noch besser.
Okay. Teleshopping-Stimme aus.
react-stinky ist ein neuer Agent Skill in meinem offenen Set, und unter dem Dauerwerbesendungs-Lack steckt ein ernsthaftes Werkzeug, gebaut um eine Bedingung: Ein Smell-Detektor ist nur so gut wie die Dinge, über die er schweigt. Hier ist, was er tut, was er sich weigert zu tun, und warum diese zweite Liste der Teil ist, der ihn behaltenswert macht.
Wonach er wirklich schnüffelt
react-stinky ist ein ganzheitlicher Code-Smell-Detektor für React und TypeScript. Das Wort, das sich hier verdient, ist ganzheitlich. Eine normale Lint-Regel betrachtet eine Zeile isoliert. react-stinky liest die ganze Komponente, den Hook, das Modul, und stellt die Fragen, die ein gründlicher Reviewer stellt. Lässt sich dieser State aus Props ableiten, statt ihn zu speichern? Macht dieser Effect einen Job, der in einen Event-Handler gehört? Kann ein Tastaturnutzer das hier überhaupt auslösen? Erlaubt diese Prop-Form einen State, der unmöglich sein sollte?
Für jeden Smell, den er findet, tut er drei Dinge, die ein Linter normalerweise nicht tut:
Er benennt die Kosten. Nicht "vermeide das", sondern "ein zusätzlicher Render und ein State-Wert, der von seinen Inputs abdriften kann". Du bekommst den echten Preis und entscheidest selbst, ob er ihn wert ist.
Er schlägt einen konkreten Fix vor, vorher und nachher, in der Sprache der Datei.
Er verlinkt die Quelle. React-Docs, MDN, das TypeScript-Handbuch, MUI. Jedes Finding ist ein Argument mit Beleg, keine Geschmacksfrage.
Die acht Säulen
Die Abdeckung umfasst acht Säulen. Sieben davon arbeiten auf einer einzelnen Datei:
Komponenten-API und Props, das Rückgrat. Benennung, Boolean- und Callback-Konventionen, String-Unions statt Boolean-Flags, Discriminated Unions, die unmögliche States verbieten, controlled und uncontrolled State, Slots und Children-Komposition, Generics, Refs, Styling-APIs, Accessibility-Props, Server-Component-Grenzen, JSDoc.
State und Datenfluss. Ableitbare Werte in useState, Props, die in State kopiert werden und dann abdriften, dieselbe Tatsache an zwei Stellen gespeichert, Prop Drilling durch Schichten, die sie nur weiterreichen.
Effects und Lifecycle. Effects, die abgeleitete Daten berechnen, Fetches und Subscriptions und Timer ohne Cleanup (Races und Leaks), Dependency-Arrays, die lügen über das, was der Effect liest.
Komponentenstruktur und Hooks. Gott-Komponenten, die Fetching, Logik und Darstellung auf einmal machen; eine Komponente, die in einer anderen definiert ist (bei jedem Render ein brandneuer Typ); konditionale Hooks.
Render-Korrektheit. Array-Index als key auf einer Liste, die umsortiert oder editiert; direkte Mutation von State oder Props; verschachtelte Ternary-Suppe im JSX; kopierte Blöcke, die einen parametrisierten Helper wollen.
Accessibility im Markup.onClick auf einem <div> ohne Rolle, ohne tabIndex und ohne Tastatur-Handler; Div-Suppe, wo semantische Elemente hingehören; Formularfelder ohne zugeordnetes Label.
TypeScript-Disziplin.any und as any und @ts-ignore, lügende as-Casts, Non-Null-! auf einem Wert, der null sein kann.
Die achte Säule, dateiübergreifende Duplikation, läuft nur im Ordner- und Repo-Scope, weil sie Dateien gegeneinander vergleicht: eine wiederverwendbare Komponente, die woanders inline nachgebaut ist, ein Hook, der kopiert statt geteilt wird, ein Typ, der an zwei Stellen deklariert ist und leise auseinanderdriftet.
Wie er den Mief bewertet
Nicht jeder Smell ist ein Notfall, und ein Werkzeug, das alle gleich behandelt, bringt dir bei, es zu ignorieren. react-stinky sortiert jedes Finding in drei Stufen.
Die drei Stufen des Gestanks
Rancid (ranzig). Ein echter Bug oder ein Bruch in Korrektheit,
Accessibility oder an der Server-Grenze. Jetzt fixen. Ein Control, das keine
Tastatur erreicht, ein mutiertes State-Array, value || 50, das leise
value={0} frisst, eine Funktion über die Server-Grenze gereicht.
Funky. Eine echte Wartbarkeitsbremse, kein Bug. Sollte gefixt werden. Eine
Boolean-Explosion, die eine Union will, eine Gott-Komponente, ein Effect, der
Daten berechnet, die du beim Render ableiten könntest.
Whiff (ein Hauch). Klein oder stilistisch. Optional. Ein nacktes
loading-Prop, JSDoc, das nur den Namen wiederholt. Real, aber kein Grund,
eine Änderung zu blockieren.
Die Stufe ist der Punkt. Sie ist der Unterschied zwischen einem Report, den du von oben bis unten abarbeitest, und einem, den du überfliegst und schließt.
Der Teil, der ihn brauchbar macht
Das ist die These, also bekommt sie ihren eigenen Abschnitt. Jeder kann einen Checker schreiben, der jedes onClick, jedes any und jeden Index-Key meldet. Zurück bekommst du eine Wand aus Lärm, die du wegzuscrollen lernst, und das ist schlimmer als gar kein Werkzeug, weil die echten Probleme jetzt unter den falschen begraben liegen.
react-stinky trägt eine Schutzleine. Jeder Smell im Katalog hat eine "Nicht melden"-Zeile, und eine Handvoll Regeln zieht sich durch alle.
Native HTML-Attribute bleiben nackt. Er sagt dir nicht, du sollst disabled in isDisabled umbenennen oder onChange an einem echten <input> in onValueChange.
Etablierte Library-Konventionen sind keine Smells. MUIs open, slots und sx; Radix' asChild. Spricht die Datei diesen Dialekt schon, spricht react-stinky ihn zurück, statt ihn zu korrigieren.
Config-Objekt-Props sind richtig für datengetriebene Komponenten mit fester Anordnung, etwa ein Data Grid. Nicht jedes Config-Array will Compound Components werden.
Ein Effect ist das richtige Werkzeug für echte externe Synchronisation. Er meldet den Effect, der abgeleiteten State berechnet, nicht den, der mit einem Websocket, localStorage oder einem Nicht-React-Widget spricht.
Index-Keys sind in Ordnung auf einer statischen Liste, die nie umsortiert, einfügt oder löscht. Der Bug taucht erst auf, wenn sich Items bewegen.
Ein Finding pro echtem Problem, und der kleinste Fix, der den Smell entfernt. Eine konsistente lokale Konvention schlägt den Katalog-Standard.
Was er bewusst nicht anfasst
react-stinky hat Nachbarn, und er bleibt aus deren Gärten. Zwei Themen reicht er absichtlich weiter:
Memoisierung (useMemo, useCallback, React.memo) geht an den react-compiler-Skill.
Sind diese Skills nicht installiert, notiert react-stinky das Finding in einer Zeile und geht weiter. Er baut sie nicht nach, und er bläht seinen Report nicht mit einer Kategorie auf, die ein anderes Werkzeug besitzt. Ein Skill, der alles versucht, kann nichts richtig, also zieht dieser eine harte Kante um React- und TypeScript-Wartbarkeit und hört dort auf.
Passe den Scope an die Frage an
Du richtest react-stinky auf so viel oder so wenig, wie du willst, und er passt die Arbeit an die Frage an.
Scope
Auslöser
Was er liest
Fragment
eine eingefügte Funktion
nur diese Fläche, mit Angabe, was er über alles Off-Screen annahm
Datei
eine oder mehrere genannte Dateien
jede Datei vollständig, jede Komponente, jeden Hook, jedes Prop-Interface
Ordner
ein Verzeichnis
den Ordner, plus den dateiübergreifenden Duplikations-Durchlauf
Repo-Sweep
"smell-check die Codebasis"
die Komponenten, Hooks und Module, mit Vorrang für geteilten und exportierten Code
Eine Ehrlichkeitsregel ist eingebaut. Im Einzeldatei- oder Fragment-Scope, wo er andere Dateien nicht sehen kann, sagt er dir, dass dateiübergreifende Duplikation nicht geprüft wurde, statt zu suggerieren, der Code sei einzigartig. Das Werkzeug sagt, was es nicht angeschaut hat, nicht nur, was es gefunden hat.
Wie ein Report aussieht
Lass ihn laufen, und du bekommst Findings nach Datei sortiert, jedes mit Stufe, Ort, Kosten, einem Vorher-Nachher-Fix und einer Quelle.
Smell: ein <Stack> (rendert ein div) hat onClick, aber keine role, tabIndex oder Tastatur-Handler.
Kosten: Tastatur- und Screenreader-Nutzer können es nicht auslösen; für assistive Technik unsichtbar.
Fix: ein echtes Control rendern (component="button" oder eine IconButton), oder
role="button" tabIndex={0} und ein onKeyDown für Enter und Space ergänzen.
Quelle: MDN button role
[Funky] effect-for-derived (state and effects), Zeile 40
Smell: ein useEffect plus setState berechnet `fullName` aus `first` und `last`.
Kosten: ein zusätzlicher Render und ein State-Wert, der von seinen Inputs abdriften kann.
Fix: beim Render berechnen, const fullName = `${first} ${last}`. Effect und State löschen.
Quelle: React, You Might Not Need an Effect
Summary: 1 rancid, 1 funky across 1 file.
Übersteht nichts die Schutzleine, sagt er es klar: "Smells fresh. No maintainability smells found." Ein sauberes Gutachten ist ein Ergebnis, kein Versagen bei der Arbeitssuche.
Woher die Regeln kommen
Die Regeln, die react-stinky durchsetzt, sind nicht für den Anlass erfunden. Der Großteil von Säule 1, dem Komponenten-API-Rückgrat, kommt direkt aus Can't Maintain, einem Spiel, das ich gebaut habe und das den Blick für langlebige React-APIs schult. Es stellt zwei Versionen derselben Komponente nebeneinander, du wählst die, die besser altert, und es sagt dir warum, mit Link zur React-, TypeScript-, MDN- oder MUI-Quelle, die den Fall belegt.
react-stinky ist die andere Hälfte dieser Idee. Das Spiel bringt einem Menschen bei, einen Smell nach dem anderen zu erkennen. Der Skill nimmt denselben Katalog und lässt ihn auf einmal über eine ganze Datei oder ein ganzes Repo laufen, sodass du nicht jeden einzeln von Hand findest. Das eine schult das Auge, das andere macht den Durchlauf, und beide zitieren dieselben Quellen, damit der Fall nie bloß Geschmack ist.
Aus Can't Maintain ist inzwischen Cant geworden, ein Hub aus Vergleichsspielen, der über React hinaus inzwischen TypeScript, Git, SEO, Testing und UX erreicht. Wenn ein Smell-Report dir Lust macht, diese Dinge schneller zu fangen, bevor sie rausgehen, ist das Spiel der Ort zum Üben. Es gibt mehr über Cant auf der Projektseite.
Probier ihn aus
react-stinky installiert in jeden Agenten, der das skills.sh-Format spricht (Claude Code, Cursor, Codex, Cline, Windsurf, OpenCode):
Dann richte ihn auf eine Komponente, einen Ordner oder das ganze Repo und bitte ihn zu schnüffeln.
Dafür gibt es einen Skill
Der komplette Katalog, die Stink-Bewertungen und jede "Nicht melden"-Regel
leben als Agent Skill. Installiere ihn mit npx skills@latest add saschb2b/skills --skill react-stinky oder lies den vollständigen
react-stinky-Skill.