Contenuto generato dall’AI.
In un progetto React moderno scritto in TypeScript, mantenere il codice coerente, leggibile e sicuro non è solo una questione di stile: è una condizione necessaria per lavorare bene in team, ridurre i bug e rendere la codebase più semplice da evolvere. ESLint è uno degli strumenti più efficaci per migliorare la qualità del codice frontend, perché permette di trasformare molte decisioni tecniche in regole automatiche, verificabili dall’editor, dal terminale e dalla pipeline di continuous integration.
Perché usare ESLint in React e TypeScript
React e TypeScript aiutano a costruire applicazioni robuste, ma non garantiscono da soli uno stile uniforme né impediscono tutte le scelte rischiose. TypeScript controlla i tipi, React offre un modello dichiarativo per costruire interfacce, ma ESLint aggiunge un livello di analisi statica configurabile: può segnalare import disordinati, variabili inutilizzate, dipendenze mancanti negli hook, uso improprio di any, dipendenze cicliche tra moduli e perfino violazioni di confini architetturali tra cartelle. La differenza è importante: il compilatore ti dice se il codice è valido, mentre il linter ti aiuta a capire se quel codice rispetta le convenzioni e le regole di qualità scelte dal team.
- Codice più coerente: tutti seguono le stesse convenzioni, indipendentemente dall’editor usato e dalle preferenze personali. Questo riduce differenze inutili nei file e rende la codebase più prevedibile.
- Import ordinati: separare librerie esterne, moduli interni e file locali migliora la leggibilità. In un componente React complesso, capire subito da dove arrivano le dipendenze aiuta durante refactor, debug e code review.
- Meno errori: regole come quelle sugli hook intercettano problemi prima della produzione. Per esempio,
react-hooks/rules-of-hooksimpedisce di chiamare hook in condizioni o cicli, mentrereact-hooks/exhaustive-depsavvisa quando una dipendenza manca inuseEffect,useMemoouseCallback. - TypeScript più utile: si limita l’abuso di
anye si promuovono tipi espliciti. Una regola come@typescript-eslint/no-explicit-anynon elimina ogni eccezione possibile, ma costringe il team a trattareanycome una scelta consapevole e non come una scorciatoia predefinita. - Code review più rapide: il team discute meno di formattazione, ordinamento e convenzioni ripetitive, concentrandosi su architettura, logica di business, accessibilità, performance e manutenibilità.
Installazione dei pacchetti
Per un progetto React e TypeScript puoi partire installando ESLint e i plugin principali. Nei progetti recenti si usa spesso la flat config di ESLint, definita in un file eslint.config.js oppure eslint.config.mjs. La variante .mjs è utile quando vuoi dichiarare esplicitamente che il file di configurazione usa moduli ECMAScript, con import ed export default. La scelta tra .js e .mjs dipende anche dal campo type del tuo package.json e dalle convenzioni del progetto.
npm install -D eslint @eslint/js typescript typescript-eslint eslint-plugin-react-hooks globals eslint-config-prettier eslint-plugin-import-x eslint-import-resolver-typescript
// Esempio di eslint.config.mjs per React, TypeScript, Vite e regole sugli import
import js from "@eslint/js";
import { defineConfig, globalIgnores } from "eslint/config";
import eslintConfigPrettier from "eslint-config-prettier/flat";
import { createTypeScriptImportResolver } from "eslint-import-resolver-typescript";
import { importX } from "eslint-plugin-import-x";
import reactHooks from "eslint-plugin-react-hooks";
import globals from "globals";
import tseslint from "typescript-eslint";
const sourceFiles = ["src/**/*.{ts,tsx}"];
const typescriptFiles = [...sourceFiles, "vite.config.ts"];
const baseImportPatterns = [
{
regex: "^lodash$",
message: "Use modular imports: lodash/map, lodash/isNil, ...",
},
{
group: ["@mui/material", "@mui/material/*"],
message: "Use the project design system instead of direct MUI imports.",
},
];
const restrictedImports = (patterns) => ["error", { patterns }];
const baseJsConfig = {
extends: [js.configs.recommended],
languageOptions: { ecmaVersion: "latest", sourceType: "module" },
linterOptions: { reportUnusedDisableDirectives: "error" },
rules: {
eqeqeq: ["warn", "always", { null: "ignore" }],
"no-console": "warn",
"prefer-const": "error",
},
};
export default defineConfig([
globalIgnores(["dist/**", "@mf-types/**", "mocks/**", "public/assets/**"]),
{
files: [...typescriptFiles],
...baseJsConfig,
rules: { ...baseJsConfig.rules, "no-fallthrough": "off" },
},
{
files: ["eslint.config.mjs"],
...baseJsConfig,
},
{
files: typescriptFiles,
extends: [tseslint.configs.recommended],
rules: {
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/consistent-type-imports": "error",
},
},
{
files: sourceFiles,
languageOptions: { globals: globals.browser },
plugins: { "react-hooks": reactHooks, "import-x": importX },
settings: {
"import-x/resolver-next": [
createTypeScriptImportResolver({
alwaysTryTypes: true,
project: "tsconfig.json",
}),
],
},
rules: {
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn",
"no-restricted-imports": restrictedImports(baseImportPatterns),
"import-x/no-restricted-paths": [
"error",
{
zones: [
{ target: "./src/core", from: "./src/ui" },
{ target: "./src/ui", from: "./src/core/api" },
],
},
],
"import-x/order": [
"error",
{
groups: [["builtin", "external"], "internal", ["parent", "sibling", "index"]],
pathGroups: [
{ pattern: "@/**", group: "internal" },
{ pattern: "{.,./**}", group: "parent", position: "after" },
],
distinctGroup: false,
"newlines-between": "always",
alphabetize: { order: "asc", caseInsensitive: true },
},
],
"import-x/no-cycle": "warn",
},
},
{
files: ["vite.config.ts", "eslint.config.mjs"],
languageOptions: { globals: globals.node },
},
{
files: ["scripts/**/*.mjs"],
...baseJsConfig,
languageOptions: { ...baseJsConfig.languageOptions, globals: globals.node },
rules: { ...baseJsConfig.rules, "no-console": "off" },
},
{
files: ["public/**/*.js"],
extends: [js.configs.recommended],
languageOptions: {
ecmaVersion: 2020,
sourceType: "script",
globals: globals.browser,
},
rules: { "no-console": "off" },
},
eslintConfigPrettier,
]);Configurazione consigliata
Con le versioni recenti di ESLint è comune usare una configurazione piatta, composta da un array di blocchi. Ogni blocco può applicarsi a un insieme diverso di file tramite files, definire regole specifiche, impostare globali diversi e abilitare plugin solo dove servono. Questo approccio è molto potente nei progetti frontend reali, perché non tutti i file hanno lo stesso contesto: i componenti in src girano nel browser, vite.config.ts gira in Node, gli script in scripts/**/*.mjs possono usare output in console, mentre i file pubblici JavaScript potrebbero non essere parte del progetto TypeScript. Nel blocco TypeScript dell’esempio, @typescript-eslint/no-explicit-any viene impostata a warn: è una scelta pragmatica, utile quando vuoi migliorare progressivamente la codebase senza bloccare subito ogni merge. @typescript-eslint/consistent-type-imports, invece, può essere un error perché spinge a separare import di tipi e import runtime, rendendo più chiaro cosa finirà davvero nel bundle.
Evitare l’abuso di any
Usare any troppo spesso annulla molti vantaggi di TypeScript. Il problema non è soltanto “perdere un tipo”, ma introdurre un punto cieco nel progetto: da quel momento il compilatore non può più aiutarti a capire se stai accedendo a una proprietà inesistente, se una funzione riceve un valore non previsto o se una risposta API è stata modellata male. La regola @typescript-eslint/no-explicit-any aiuta il team a scegliere tipi più precisi e a rendere il codice più affidabile, soprattutto quando si lavora con API, form, componenti condivisi e dati provenienti dal backend. Nei casi in cui il tipo non sia ancora noto, spesso è meglio usare unknown, validare il dato e poi restringere il tipo in modo esplicito. In questo modo il codice resta flessibile, ma non rinuncia alla sicurezza.
Conclusione
Configurare ESLint in un progetto React e TypeScript è un investimento sulla qualità del codice frontend. Una configurazione ben progettata non si limita a “mettere in ordine” i file: chiarisce le aspettative del team, riduce errori ripetitivi, mantiene gli import ordinati, evidenzia dipendenze cicliche, limita l’uso indiscriminato di any e può persino proteggere l’architettura applicativa impedendo import tra aree che dovrebbero restare separate. Il consiglio è partire con poche regole ad alto impatto, usare warning dove serve una transizione graduale e trasformare le regole più importanti in errori quando il progetto è maturo. ESLint non sostituisce il giudizio dello sviluppatore, ma offre una base automatica e condivisa per scrivere codice migliore ogni giorno.

