Come risparmiare tempo in React con TypeScript

Come utilizzare TypeScript in React per risparmiare sul tempo di sviluppo

Se avete mai programmato in JavaScript, saprete perfettamente che un programmatore ha un rapporto di affetto molto profondo nei confronti dei Type Error: dimentica il nome di una variabile, di che tipo sia, o non ricorda cosa va passato ad un metodo. Questo potrebbe non essere un grosso problema se parliamo di piccole applicazioni e piccoli team. Ma se il volume di codice comincia a crescere e, come spesso accade in React, i componenti iniziano a moltiplicarsi, allora questo rapporto d’amore comincia a barcollare e queste piccole sviste e dimenticanze iniziano a consumare un bel po’ del nostro tempo.

Vediamo un esempio in React (versione > 16.8), creato con Create React App. Nella nostra app avremo 4 funzionalità: visualizzare la lista, aggiungere un nuovo elemento, eliminare un elemento dalla lista e visualizzare il paese di riferimento di quella valuta cliccando su un elemento.

alt text

Ogni valuta ha la seguente struttura:

{
  id: 3,
  name: 'RUB',
  value: 81.18,
  at: new Date()
}

Nel dettaglio: name è il nome della valuta, value è il valore corrispondente ad 1 euro, at è la data in cui è stato registrato quel valore.

Lo stato dell’applicazione è gestito, tramite useState, in questo modo:

const [currencies, setCurrencies] = useState(data);
const [newCurrency, setNewCurrency] = useState({
id: data.length,
name: '',
value: 1,
at: null
});
const [info, setInfo] = useState('');

Immaginate di essere una nuova leva del team e di leggere questo snippet di codice per la prima volta. Indipendentemente dal vostro livello di scrupoli, vi dovrebbero sorgere almeno due domande:

  • at che cosa rappresenta?
  • com’è fatto l’array di currencies?

Perché in fondo è questo il problema: quando abbiamo a che fare con stringhe, numeri e booleani, è tutto bellissimo, riusciamo ad applicare mentalmente una type inference e a dedurre il tipo di queste variabili. Ma per null cosa possiamo fare?

Vediamo un altro esempio con il form per l’aggiunta di una nuova valuta:

function AddItem(props) {
  return (
    <form onSubmit={props.handleSubmit}>
      <h1>Aggiungi nuova valuta</h1>
      <div>
        <label>Nome</label>
        <div>
          <input
            name="name"
            value={props.item.name}
            onChange={props.handleChange}
            type="text"
          />
        </div>
      </div>
      <div>
        <label>Valore corrispondente ad 1 EURO</label>
        <div>
          <input
            name="value"
            value={props.item.value}
            onChange={props.handleChange}
            type="number"
            min={1}
            step={0.01}
          />
        </div>
      </div>
      <button type="submit">Aggiungi</button>
    </form>
  )
}

Anche per i non appassionati di mistero, quel props lì in cima dovrebbe incuriosire un bel po’.

Questi problemi non sono nuovi, naturalmente, e si possono limitare in diversi modi: in minima parte già con un buon IDE, oppure utilizzando un linter o con la libreria prop-types, tanto per citarne alcuni.

Per fortuna esiste anche TypeScript. Indipendentemente da tutte le funzionalità che offre allo sviluppatore, ai suoi vantaggi e anche ai suoi svantaggi - che non tratteremo in questo breve articolo e che andrebbero considerati in base al progetto e al team di cui si dispone - concentriamoci solo su una delle tagline che questo superset di JavaScript offrirebbe:

TypeScript makes code more readable and debuggable

Ovvero: codice più leggibile e più facile da debuggare. Proviamo a riscrivere la nostra applicazione, stavolta utilizzando TypeScript. Lanciamo il comando:

npx create-react-app example2 --template typescript

che farà già tutta la configurazione al posto nostro. Ora creiamo un file types.d.ts in /src, e definiamo un tipo Currency per renderlo disponibile in tutta l’applicazione.:

type Currency = {
  id: number
  name: string
  value: number
  at: Date | null
}

Nel nostro componente App lo stato sarà il seguente:

const [currencies, setCurrencies] = useState<Currency[]>(data);
const [newCurrency, setNewCurrency] = useState<Currency>({
id: data.length,
name: '',
value: 1,
at: null
});
const [info, setInfo] = useState('');

Se ora leggessimo il codice per la prima volta sapremmo perfettamente ogni variabile che cosa contiene e che cosa dovrà gestire. Per il nostro form potremmo dichiarare un tipo per le props del componente, il quale deve ricevere:

  • un item (di tipo Currency)
  • una funzione handleChange
  • una funzione handleSubmit

Entrambe le funzioni ricevono, in ingresso, un evento del form e non restituiscono nulla. Una bozza della definizione di AddItemProps potrebbe essere:

type AddItemProps = {
  item: Currency,
  handleChange: (event) => void
  handleSubmit: (event) => void
}

Ma event lasciato così, senza un tipo, non va bene, né per noi né per TypeScript, che ci obbliga ad assegnare un tipo ad ogni input. Ragioniamoci un secondo:

  • handleChange cattura un evento change dagli input del form
  • handleSubmit cattura un evento submit dal form

A questo punto, riscriviamo le nostre props in questo modo:

type AddItemProps = {
  item: Currency,
  handleChange: (event: React.ChangeEvent<HTMLInputElement>) => void
  handleSubmit: (event: React.FormEvent<HTMLFormElement>) => void
}

Non c’è bisogno di continuare: risulta chiaro che ora possiamo definire i tipi degli ingressi e delle uscite dei nostri componenti, dei metodi e delle variabili in generale. Inoltre, TypeScript ci segnalerà eventuali sviste o ci aiuterà a ricordare nomi e proprietà.

Qui sotto, ad esempio, dopo il . ci segnala l’elenco delle proprietà dell’item che abbiamo definito una riga sopra con il tipo Currency.

alt text

In quest’altra immagine, invece, ci segnala che value non è una stringa ma un numero e che stiamo sbagliando ad inizializzarla.

alt text

Tutto fantastico, certo, ma potreste pensare: “Non è quello che faccio già con la libreria prop-types?”. Beh, no, c’è una differenza sostanziale. La libreria prop-types effettua un controllo runtime; TypeScript, invece, agisce in compile time. Insomma: ci aiuta mentre scriviamo il codice!