TypeScript cu React
Acest capitol arată cum să pornești un proiect React cu TypeScript și cum să tipizezi lucrurile pe care le întâlnești zilnic: props, evenimente, stare, ref-uri, copii (children), preluarea datelor și contextul.
De ce să folosești TypeScript cu React?
TypeScript îmbunătățește React cu:
- Siguranța tipurilor pentru props, stare și context
- Autocompletare și refactorizare mai bune în IDE
- Erori prinse devreme, chiar în timp ce scrii codul
Notă: Acest capitol presupune că știi bazele React. Dacă ești începător în React, parcurge mai întâi un tutorial de React.
Primii pași
Creează o nouă aplicație React + TypeScript cu Vite rulând aceste comenzi:
npm create vite@latest my-app -- --template react-tscd my-appnpm installnpm run dev
Fișierul tău tsconfig.json ar trebui să aibă aceste compilerOptions recomandate:
"target": "ES2020""lib": ["ES2020", "DOM", "DOM.Iterable"]"module": "ESNext""moduleResolution": "Node""jsx": "react-jsx""strict": true"skipLibCheck": true"noEmit": true"resolveJsonModule": true"allowSyntheticDefaultImports": true"esModuleInterop": true"forceConsistentCasingInFileNames": true
împreună cu "include": ["src"] alături.
Notă: Păstrează strict activat pentru cea mai bună siguranță a tipurilor. Aceste opțiuni funcționează bine cu Vite și Create React App.
Tipizarea componentelor
Descrie props-urile cu un tip, apoi folosește acel tip într-o componentă funcție:
Props tipizate într-o componentă funcție
GreetingProps are un name obligatoriu și un age opțional; componenta afișează linia cu vârsta doar când aceasta este dată.
// Greeting.tsx
type GreetingProps = {
name: string;
age?: number;
};
export function Greeting({ name, age }: GreetingProps) {
return (
<div>
<h2>Hello, {name}!</h2>
{age !== undefined && <p>You are {age} years old</p>}
</div>
);
}
Tipare frecvente
Acestea sunt tiparele de tipizare pe care le vei folosi cel mai des într-o aplicație React.
Evenimente sigure ca tip. Dă handler-elor de evenimente pentru câmpuri de intrare și butoane tipul de eveniment React potrivit.
Tipizarea stării cu useState. Folosește argumente de tip explicite pentru numere, uniuni și valori care pot fi null.
useRef cu elemente DOM. Tipizează un ref cu elementul DOM pe care îl va conține, ca să îi poți citi proprietățile în siguranță.
Tipizarea copiilor (children). Acceptă copiii cu tipul React.ReactNode.
Funcții ajutătoare de fetch cu generice. Folosește o funcție generică pentru a tipiza răspunsurile API.
Context minimal și hook personalizat. Oferă un context mic și tipizat, plus un hook ajutător care garantează că este disponibil.
Tipurile TypeScript pentru Vite: Adaugă tipurile ambientale ale lui Vite, ca să nu lipsească nimic. Creează src/vite-env.d.ts care să conțină /// <reference types="vite/client" /> sau adaugă "types": ["vite/client"] în compilerOptions din tsconfig.json.
Despre React.FC: Preferă componente funcție tipizate simplu. React.FC este opțional; adaugă children implicit, dar nu este obligatoriu.
baseUrl și paths opționale: Acestea pot scurta importurile dacă bundler-ul tău le suportă. Setează "baseUrl": "." și "paths": { "@/*": ["src/*"] } în compilerOptions. Configurează-le doar dacă uneltele tale (de exemplu Vite sau tsconfig-paths) sunt pregătite pentru alias-uri de căi.
Handler-e de evenimente sigure ca tip
React.ChangeEvent<HTMLInputElement> și React.MouseEvent<HTMLButtonElement> îți dau acces tipizat la eveniment și la ținta lui.
// Input change
function NameInput() {
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
console.log(e.target.value);
}
return <input onChange={handleChange} />;
}
// Button click
function SaveButton() {
function handleClick(e: React.MouseEvent<HTMLButtonElement>) {
e.preventDefault();
}
return <button onClick={handleClick}>Save</button>;
}
Tipizarea stării cu useState
Un număr, o uniune de literale de tip șir de caractere și un obiect care poate fi null, fiecare cu un argument de tip explicit.
const [count, setCount] = React.useState<number>(0);
const [status, setStatus] = React.useState<'idle' | 'loading' | 'error'>('idle');
type User = { id: string; name: string };
const [user, setUser] = React.useState<User | null>(null);
useRef cu un element DOM
Ref-ul este tipizat ca HTMLInputElement, așa că inputRef.current?.select() este verificat de compilator.
function FocusInput() {
const inputRef = React.useRef<HTMLInputElement>(null);
return <input ref={inputRef} onFocus={() => inputRef.current?.select()} />;
}
Tipizarea copiilor (children)
children este opțional și tipizat ca React.ReactNode, așa că este acceptat orice poate afișa React.
type CardProps = { title: string; children?: React.ReactNode };
function Card({ title, children }: CardProps) {
return (
<div>
<h2>{title}</h2>
{children}
</div>
);
}
Funcție ajutătoare generică de fetch
fetchJson<T> returnează un Promise<T>, așa că apelantul decide forma răspunsului.
async function fetchJson<T>(url: string): Promise<T> {
const res = await fetch(url);
if (!res.ok) throw new Error('Network error');
return res.json() as Promise<T>;
}
// Usage inside an async function/component effect
async function loadPosts() {
type Post = { id: number; title: string };
const posts = await fetchJson<Post[]>("/api/posts");
console.log(posts);
}
Context tipizat și hook personalizat
Valoarea contextului este fie obiectul temei, fie null; useTheme aruncă o eroare dacă este folosit în afara provider-ului.
type Theme = 'light' | 'dark';
const ThemeContext = React.createContext<{ theme: Theme; toggle(): void } | null>(null);
function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setTheme] = React.useState<Theme>('light');
const value = { theme, toggle: () => setTheme(t => (t === 'light' ? 'dark' : 'light')) };
return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
}
function useTheme() {
const ctx = React.useContext(ThemeContext);
if (!ctx) throw new Error('useTheme must be used within ThemeProvider');
return ctx;
}