🚀 Créer un projet React + TypeScript
# Vite (recommandé)
npm create vite@latest mon-app -- --template react-ts
cd mon-app && npm install
# Create React App (legacy)
npx create-react-app mon-app --template typescript
Vite + React + TS = le setup le plus rapide en 2024. Les fichiers auront l'extension .tsx pour les composants avec JSX.
🧩 Typer les Props
// Avec interface (recommandé)
interface ButtonProps {
label: string;
onClick: () => void;
variant?: "primary" | "secondary" | "danger";
disabled?: boolean;
children?: React.ReactNode;
}
const Button = ({ label, onClick, variant = "primary", disabled }: ButtonProps) => {
return (
<button
className={`btn btn-${variant}`}
onClick={onClick}
disabled={disabled}
>
{label}
</button>
);
};
// Avec React.FC (optionnel — moins préféré aujourd'hui)
const Button: React.FC<ButtonProps> = ({ label, onClick }) => (
<button onClick={onClick}>{label}</button>
);
🪝 Hooks typés
import { useState, useRef, useEffect } from "react";
// useState<T>
const [count, setCount] = useState<number>(0);
const [user, setUser] = useState<User | null>(null);
// useRef<T> — référence DOM
const inputRef = useRef<HTMLInputElement>(null);
// inputRef.current peut être null → vérifier avant usage
inputRef.current?.focus();
// useRef pour valeur mutable (pas DOM)
const timerRef = useRef<ReturnType<typeof setTimeout>>();
// useEffect
useEffect(() => {
const controller = new AbortController();
fetchUser(userId, controller.signal).then(setUser);
return () => controller.abort();
}, [userId]);
🎯 Événements typés
// Événements formulaires
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setValue(e.target.value);
};
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
// traitement
};
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
console.log(e.currentTarget.id);
};
// Générique pour DRY
type InputHandler = React.ChangeEventHandler<HTMLInputElement>;
const handleInput: InputHandler = (e) => setValue(e.target.value);
🔗 Context API typé
import { createContext, useContext, useState } from "react";
interface AuthContext {
user: User | null;
login: (credentials: Credentials) => Promise<void>;
logout: () => void;
}
const AuthContext = createContext<AuthContext | null>(null);
export function useAuth(): AuthContext {
const ctx = useContext(AuthContext);
if (!ctx) throw new Error("useAuth must be used within AuthProvider");
return ctx;
}
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const login = async (creds: Credentials) => { /* ... */ };
const logout = () => setUser(null);
return <AuthContext.Provider value={{ user, login, logout }}>{children}</AuthContext.Provider>;
}
🪝 Custom Hook typé
// useFetch — hook générique pour les requêtes
interface FetchState<T> {
data: T | null;
loading: boolean;
error: Error | null;
}
function useFetch<T>(url: string): FetchState<T> {
const [state, setState] = useState<FetchState<T>>({
data: null, loading: true, error: null
});
useEffect(() => {
fetch(url)
.then(r => r.json())
.then((data: T) => setState({ data, loading: false, error: null }))
.catch((error: Error) => setState({ data: null, loading: false, error }));
}, [url]);
return state;
}
// Utilisation
const { data, loading, error } = useFetch<User[]>("/api/users");