Introduzione
TypeScript è un superset di JavaScript che aggiunge type safety statica. Con React, TypeScript permette di definire tipi per componenti, props, state e altri elementi, aiutando a prevenire errori durante lo sviluppo invece che a runtime. Questo articolo spiega come funziona TypeScript con React.
Cos’è TypeScript
TypeScript estende JavaScript aggiungendo annotazioni di tipo. Il codice TypeScript viene compilato in JavaScript prima dell’esecuzione:
// JavaScript: nessun controllo di tipofunction add(a, b) { return a + b;}add("2", "5"); // "25" (concatenazione)
// TypeScript: type annotationsfunction add(a: number, b: number): number { return a + b;}add("2", "5"); // ❌ Errore: string non assegnabile a numberTypeScript usa type inference quando possibile, quindi non è necessario annotare ogni tipo esplicitamente.
Tipi Base
Primitivi
let age: number = 30;let username: string = "John";let isInstructor: boolean = true;Array
let hobbies: string[] = ["reading", "coding"];// oppurelet hobbies: Array<string> = ["reading", "coding"];Oggetti
let person: { name: string; age: number;} = { name: "John", age: 30};Type Inference
TypeScript inferisce i tipi quando si assegna un valore immediatamente:
let course = "React Guide"; // TypeScript inferisce: stringcourse = 123; // ❌ Errore: number non assegnabile a stringUnion Types
Permettono di accettare più tipi:
let course: string | number = "React";course = 123; // ✅ ValidoType Aliases
Evitano duplicazione di definizioni di tipo:
type Person = { name: string; age: number;};
let person: Person = { name: "John", age: 30 };let people: Person[] = [person];Funzioni e Tipi
function add(a: number, b: number): number { return a + b;}
// Return type void per funzioni che non ritornano nullafunction printOutput(value: any): void { console.log(value);}Il return type può essere omesso se TypeScript lo può inferire.
Generics
I generics permettono di creare funzioni type-safe ma flessibili:
function insertAtBeginning<T>(array: T[], value: T): T[] { return [value, ...array];}
const numbers = insertAtBeginning([1, 2, 3], 0); // T inferito come numberconst strings = insertAtBeginning(["a", "b"], "c"); // T inferito come stringT è un placeholder di tipo che viene inferito quando si chiama la funzione.
Array come Generic Type
number[] è syntactic sugar per Array<number>. Tutti gli array sono di tipo Array, ma il tipo ha senso solo se si specifica il tipo degli elementi:
let numbers: Array<number> = [1, 2, 3];// equivalente alet numbers: number[] = [1, 2, 3];Setup React con TypeScript
Creare un progetto React con TypeScript:
npx create-react-app my-app --template typescriptI file hanno estensione .tsx invece di .jsx quando contengono JSX. Il TypeScript compiler viene eseguito automaticamente durante lo sviluppo e il build.
Dipendenze
Oltre a typescript, sono necessari i pacchetti @types per le librerie JavaScript:
{ "dependencies": { "react": "^18.0.0", "@types/react": "^18.0.0", "@types/react-dom": "^18.0.0" }}Componenti Funzionali con Props
I componenti funzionali usano React.FC (Functional Component) come tipo:
import React from 'react';
type TodosProps = { items: string[];};
const Todos: React.FC<TodosProps> = (props) => { return ( <ul> {props.items.map(item => ( <li key={item}>{item}</li> ))} </ul> );};
export default Todos;React.FC è un generic type che merge le props custom con le props base (come children). Le angle brackets (<>) specificano il tipo concreto per questa istanza del generic.
React.FC e Generic Types
Quando si usa React.FC<PropsType>, si sta usando un generic type già definito da React. Le angle brackets non creano un nuovo generic, ma specificano il tipo concreto da usare per questa istanza:
// React.FC è definito internamente come:// type FC<P = {}> = (props: P & { children?: ReactNode }) => JSX.Element
// Quando si scrive:const Component: React.FC<MyProps> = (props) => { ... }
// Si sta dicendo: usa React.FC con P = MyProps// Quindi props avrà tipo MyProps & { children?: ReactNode }Modelli di Dati con Classi
Le classi possono essere usate come modelli di dati e come tipi:
export class Todo { id: string; text: string;
constructor(todoText: string) { this.text = todoText; this.id = new Date().toISOString(); }}La classe può essere usata sia come costruttore che come tipo:
import { Todo } from './models/todo';
const todos: Todo[] = [ new Todo("Learn React"), new Todo("Learn TypeScript")];State con TypeScript
useState è un generic function, quindi si può specificare il tipo dello state:
import { useState } from 'react';import { Todo } from './models/todo';
function App() { // Specificare il tipo per evitare che TypeScript inferisca "never[]" const [todos, setTodos] = useState<Todo[]>([]);
const addTodoHandler = (text: string) => { const newTodo = new Todo(text); setTodos(prevTodos => prevTodos.concat(newTodo)); };
return <Todos items={todos} />;}Senza specificare il tipo, TypeScript inferisce never[] per un array vuoto iniziale.
Refs con TypeScript
I refs devono specificare il tipo dell’elemento HTML a cui si connettono:
import { useRef, FormEvent } from 'react';
function NewTodo() { const todoTextInputRef = useRef<HTMLInputElement>(null);
const submitHandler = (event: FormEvent) => { event.preventDefault(); const enteredText = todoTextInputRef.current!.value; // ... };
return ( <form onSubmit={submitHandler}> <input type="text" id="text" ref={todoTextInputRef} /> <button>Add Todo</button> </form> );}useRef<HTMLInputElement>(null): specifica che il ref conterrà unHTMLInputElementFormEvent: tipo dell’evento per form submission!: operatore di non-null assertion quando si è certi che il valore non sia null
Operatori ? e !
Quando si accede a proprietà che potrebbero essere null/undefined:
-
?(optional chaining): prova ad accedere, ritornaundefinedse nullconst value = ref.current?.value; // string | undefined -
!(non-null assertion): asserisce che il valore non è nullconst value = ref.current!.value; // string
Usare ! solo quando si è certi che il valore non sia null.
Props con Funzioni
Quando si passano funzioni come props, si definisce il tipo della funzione:
type NewTodoProps = { onAddTodo: (text: string) => void;};
const NewTodo: React.FC<NewTodoProps> = (props) => { const submitHandler = (event: FormEvent) => { // ... props.onAddTodo(enteredText); }; // ...};La sintassi (text: string) => void definisce una funzione che:
- Prende un parametro
textdi tipostring - Ritorna
void(nessun valore)
Context API con TypeScript
Il Context deve definire il tipo del valore che gestisce:
import { createContext, useState, ReactNode } from 'react';import { Todo } from './models/todo';
type TodosContextObj = { items: Todo[]; addTodo: (text: string) => void; removeTodo: (id: string) => void;};
export const TodosContext = createContext<TodosContextObj>({ items: [], addTodo: () => {}, removeTodo: () => {}});
export const TodosContextProvider: React.FC<{ children: ReactNode }> = (props) => { const [todos, setTodos] = useState<Todo[]>([]);
const addTodoHandler = (text: string) => { const newTodo = new Todo(text); setTodos(prevTodos => prevTodos.concat(newTodo)); };
const removeTodoHandler = (id: string) => { setTodos(prevTodos => prevTodos.filter(todo => todo.id !== id)); };
const contextValue: TodosContextObj = { items: todos, addTodo: addTodoHandler, removeTodo: removeTodoHandler };
return ( <TodosContext.Provider value={contextValue}> {props.children} </TodosContext.Provider> );};createContext è un generic function che accetta il tipo del context value. Il tipo viene usato per type-checking quando si usa useContext.
tsconfig.json
Il file tsconfig.json configura il TypeScript compiler:
{ "compilerOptions": { "target": "es5", "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "strict": true, "jsx": "react-jsx" }}Opzioni principali:
target: versione JavaScript di output (es.es5,es2020)lib: librerie di tipo incluse (es.domper tipi DOM comeHTMLInputElement)strict: abilita controlli rigorosi (es. no implicitany)jsx: come gestire JSX (react-jsxper React 17+)
Strict Mode
Con "strict": true, TypeScript:
- Non permette implicit
any(devi esplicitare i tipi) - Controlla null/undefined più rigorosamente
- Abilita altri controlli di type safety
Se un parametro non ha tipo e TypeScript non può inferirlo, si ottiene un errore invece di any implicito.
Riepilogo
- TypeScript: Superset di JavaScript che aggiunge type safety statica; il codice viene compilato in JavaScript
- Tipi base:
number,string,boolean, array (T[]oArray<T>), oggetti, union types (string | number) - Type inference: TypeScript inferisce i tipi quando possibile; annotare esplicitamente solo quando necessario
- Type aliases: Usare
typeper evitare duplicazione di definizioni di tipo - Generics: Permettono funzioni type-safe ma flessibili usando placeholder di tipo (
<T>) - React.FC: Tipo per componenti funzionali; generic che merge props custom con props base
- useState: Generic function; specificare il tipo per evitare
never[]con array vuoti iniziali - Refs: Specificare il tipo dell’elemento HTML (
useRef<HTMLInputElement>(null)) - Event types:
FormEventper form submission,MouseEventper click, ecc. - Context:
createContextè generic; definire il tipo del context value - tsconfig.json: Configura il compiler;
strict: trueabilita controlli rigorosi