Pourquoi React ?
React est la bibliothèque JavaScript la plus populaire pour construire des interfaces utilisateur. En 2025, elle domine avec plus de 15 millions de téléchargements hebdomadaires sur NPM.
Avantages clés
- Composants réutilisables : Créez une fois, utilisez partout
- Virtual DOM : Mises à jour ultra-rapides
- Écosystème riche : Milliers de bibliothèques compatibles
- TypeScript natif : Support de premier ordre
- React 18+ : Concurrent rendering et Suspense
1. Premiers pas : Créer un projet React
Avec Vite (Recommandé en 2025)
# Créer un nouveau projet
npm create vite@latest mon-app-react -- --template react-ts
# Naviguer et installer
cd mon-app-react
npm install
# Lancer le serveur de développement
npm run dev
Structure du projet
mon-app-react/
├── node_modules/
├── public/
├── src/
│ ├── App.tsx
│ ├── App.css
│ ├── main.tsx
│ └── vite-env.d.ts
├── index.html
├── package.json
├── tsconfig.json
└── vite.config.ts
2. Composants : Les briques de votre application
Votre premier composant
// src/components/Welcome.tsx
const Welcome = () => {
return (
# Bienvenue dans React !
Votre premier composant fonctionne.
);
};
export default Welcome;
JSX : JavaScript + XML
// JSX permet d'écrire du HTML dans JavaScript
const element = # Hello, world!;
// Équivalent JavaScript pur (ne pas utiliser!)
const element = React.createElement('h1', null, 'Hello, world!');
// Expressions JavaScript dans JSX
const name = 'Marie';
const element = # Bonjour, {name}!;
// Attributs en camelCase
const button = (
);
Composant avec structure complète
// src/components/UserProfile.tsx
import { CSSProperties } from 'react';
interface UserProfileProps {
name: string;
age: number;
avatar?: string;
}
const UserProfile = ({ name, age, avatar }: UserProfileProps) => {
const cardStyle: CSSProperties = {
border: '1px solid #ddd',
borderRadius: '8px',
padding: '20px',
maxWidth: '300px',
textAlign: 'center'
};
return (
{avatar && (
)}
## {name}
Age: {age} ans
);
};
export default UserProfile;
Utiliser votre composant
// src/App.tsx
import UserProfile from './components/UserProfile';
function App() {
return (
# Notre équipe
);
}
export default App;
3. Props : Passer des données aux composants
Props de base
// Button.tsx
interface ButtonProps {
text: string;
variant?: 'primary' | 'secondary' | 'danger';
disabled?: boolean;
onClick?: () => void;
}
const Button = ({
text,
variant = 'primary',
disabled = false,
onClick
}: ButtonProps) => {
return (
}
disabled={disabled}
onClick={onClick}
>
{text}
);
};
// Utilisation
Props.children : Contenu imbriqué
// Card.tsx
interface CardProps {
title: string;
children: React.ReactNode;
footer?: React.ReactNode;
}
const Card = ({ title, children, footer }: CardProps) => {
return (
### {title}
{children}
{footer && (
{footer}
)}
);
};
// Utilisation
}
>
Nom: Sophie Dupont
Email: sophie@example.com
Props avec spread operator
// Input.tsx
interface InputProps extends React.InputHTMLAttributes {
label: string;
error?: string;
}
const Input = ({ label, error, ...inputProps }: InputProps) => {
return (
{error && {error}}
);
};
// Utilisation
4. State : Données dynamiques et interactivité
useState : Le hook fondamental
import { useState } from 'react';
const Counter = () => {
// [valeur, fonction pour modifier la valeur]
const [count, setCount] = useState(0);
const increment = () => setCount(count + 1);
const decrement = () => setCount(count - 1);
const reset = () => setCount(0);
return (
## Compteur: {count}
);
};
State avec types complexes
interface Todo {
id: number;
text: string;
completed: boolean;
}
const TodoList = () => {
const [todos, setTodos] = useState([]);
const [inputValue, setInputValue] = useState('');
const addTodo = () => {
if (inputValue.trim()) {
const newTodo: Todo = {
id: Date.now(),
text: inputValue,
completed: false
};
setTodos([...todos, newTodo]);
setInputValue('');
}
};
const toggleTodo = (id: number) => {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
));
};
const deleteTodo = (id: number) => {
setTodos(todos.filter(todo => todo.id !== id));
};
return (
## Ma Todo List
setInputValue(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && addTodo()}
placeholder="Nouvelle tâche..."
/>
{todos.map(todo => (
-
toggleTodo(todo.id)}
/>
{todo.text}
))}
{todos.filter(t => !t.completed).length} tâches restantes
);
};
State avec objets
interface FormData {
username: string;
email: string;
age: number;
newsletter: boolean;
}
const RegistrationForm = () => {
const [formData, setFormData] = useState({
username: '',
email: '',
age: 18,
newsletter: false
});
// ✅ Bonne pratique : fonction de mise à jour
const updateField = (field: keyof FormData, value: any) => {
setFormData(prev => ({
...prev,
[field]: value
}));
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
console.log('Données du formulaire:', formData);
// Envoyer à l'API...
};
return (
);
};
5. Événements et handlers
Événements courants
const EventExamples = () => {
// Click
const handleClick = (e: React.MouseEvent) => {
console.log('Cliqué!', e.currentTarget);
};
// Input change
const handleChange = (e: React.ChangeEvent) => {
console.log('Valeur:', e.target.value);
};
// Form submit
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
console.log('Formulaire soumis');
};
// Key press
const handleKeyPress = (e: React.KeyboardEvent) => {
if (e.key === 'Enter') {
console.log('Enter pressé');
}
};
// Focus
const handleFocus = (e: React.FocusEvent) => {
e.target.select(); // Sélectionne tout le texte
};
return (
);
};
Passer des paramètres aux handlers
const ProductList = () => {
const products = [
{ id: 1, name: 'Laptop', price: 999 },
{ id: 2, name: 'Mouse', price: 29 },
{ id: 3, name: 'Keyboard', price: 79 }
];
// Méthode 1: Arrow function inline (simple mais crée une nouvelle fonction à chaque render)
const handleBuy1 = (productId: number) => {
console.log('Achat produit:', productId);
};
// Méthode 2: Currying (plus performant)
const handleBuy2 = (productId: number) => () => {
console.log('Achat produit:', productId);
};
return (
{products.map(product => (
### {product.name}
{product.price}€
{/ Méthode 1 /}
{/ Méthode 2 (préférée) /}
))}
);
};
6. Listes et clés
Rendu de listes
interface User {
id: number;
name: string;
email: string;
active: boolean;
}
const UserList = () => {
const users: User[] = [
{ id: 1, name: 'Alice', email: 'alice@example.com', active: true },
{ id: 2, name: 'Bob', email: 'bob@example.com', active: false },
{ id: 3, name: 'Charlie', email: 'charlie@example.com', active: true }
];
// ✅ Bonne pratique : key unique et stable
return (
{users.map(user => (
-
{user.name}
{user.email}
))}
);
};
// ❌ Mauvaise pratique : index comme key
users.map((user, index) => (
{user.name} // Problèmes avec réorganisation
));
// ❌ Mauvaise pratique : pas de key
users.map(user => (
{user.name} // Warning React
));
Listes filtrées et triées
const AdvancedUserList = () => {
const [users, setUsers] = useState([...]);
const [filter, setFilter] = useState<'all' | 'active' | 'inactive'>('all');
const [sortBy, setSortBy] = useState<'name' | 'email'>('name');
// Filtrage
const filteredUsers = users.filter(user => {
if (filter === 'active') return user.active;
if (filter === 'inactive') return !user.active;
return true;
});
// Tri
const sortedUsers = [...filteredUsers].sort((a, b) => {
return a[sortBy].localeCompare(b[sortBy]);
});
return (
{sortedUsers.map(user => (
-
{user.name} - {user.email}
))}
Total: {sortedUsers.length} utilisateurs
);
};
7. Rendu conditionnel
Méthodes de rendu conditionnel
const ConditionalRendering = () => {
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [user, setUser] = useState(null);
const [status, setStatus] = useState<'loading' | 'success' | 'error'>('loading');
// Méthode 1: if/else
if (!isLoggedIn) {
return ;
}
// Méthode 2: Ternaire
return (
{isLoggedIn ? (
) : (
)}
);
// Méthode 3: && (pour un seul cas)
return (
{user && }
{error && }
);
// Méthode 4: Switch avec objet
const statusComponents = {
loading: ,
success: ,
error:
};
return (
{statusComponents[status]}
);
};
8. Exemple complet : Application de recherche
// SearchApp.tsx
import { useState, useEffect } from 'react';
interface Article {
id: number;
title: string;
excerpt: string;
category: string;
tags: string[];
}
const SearchApp = () => {
const [articles, setArticles] = useState([]);
const [searchTerm, setSearchTerm] = useState('');
const [selectedCategory, setSelectedCategory] = useState('all');
const [loading, setLoading] = useState(true);
// Simuler un fetch API
useEffect(() => {
const fetchArticles = async () => {
setLoading(true);
// Dans la vraie vie: await fetch('/api/articles')
const mockArticles: Article[] = [
{
id: 1,
title: 'Introduction à React',
excerpt: 'Apprenez les bases de React',
category: 'Tutorial',
tags: ['react', 'javascript']
},
{
id: 2,
title: 'Next.js pour les débutants',
excerpt: 'Créez des apps performantes',
category: 'Tutorial',
tags: ['nextjs', 'ssr']
},
{
id: 3,
title: 'TypeScript Best Practices',
excerpt: 'Écrivez du TypeScript propre',
category: 'Guide',
tags: ['typescript', 'best-practices']
}
];
setTimeout(() => {
setArticles(mockArticles);
setLoading(false);
}, 1000);
};
fetchArticles();
}, []);
// Filtrage
const filteredArticles = articles.filter(article => {
const matchesSearch = article.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
article.excerpt.toLowerCase().includes(searchTerm.toLowerCase());
const matchesCategory = selectedCategory === 'all' || article.category === selectedCategory;
return matchesSearch && matchesCategory;
});
// Extraire les catégories uniques
const categories = ['all', ...new Set(articles.map(a => a.category))];
if (loading) {
return Chargement...;
}
return (
# Recherche d'articles
setSearchTerm(e.target.value)}
className="search-input"
/>
{filteredArticles.length} résultat(s)
{filteredArticles.length === 0 ? (
Aucun article trouvé pour "{searchTerm}"
) : (
{filteredArticles.map(article => (
## {article.title}
{article.excerpt}
{article.category}
{article.tags.map(tag => (
#{tag}
))}
))}
)}
);
};
export default SearchApp;
9. Tests avec React Testing Library
// SearchApp.test.tsx
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import SearchApp from './SearchApp';
describe('SearchApp', () => {
test('affiche le loading initialement', () => {
render( );
expect(screen.getByText('Chargement...')).toBeInTheDocument();
});
test('affiche les articles après chargement', async () => {
render( );
await waitFor(() => {
expect(screen.getByText('Introduction à React')).toBeInTheDocument();
});
});
test('filtre les articles par recherche', async () => {
const user = userEvent.setup();
render( );
await waitFor(() => {
expect(screen.getByText('Introduction à React')).toBeInTheDocument();
});
const searchInput = screen.getByPlaceholderText('Rechercher un article...');
await user.type(searchInput, 'Next.js');
expect(screen.getByText('Next.js pour les débutants')).toBeInTheDocument();
expect(screen.queryByText('Introduction à React')).not.toBeInTheDocument();
});
test('filtre par catégorie', async () => {
render( );
await waitFor(() => {
expect(screen.getByText('Introduction à React')).toBeInTheDocument();
});
const categorySelect = screen.getByRole('combobox');
fireEvent.change(categorySelect, { target: { value: 'Guide' } });
expect(screen.getByText('TypeScript Best Practices')).toBeInTheDocument();
expect(screen.queryByText('Introduction à React')).not.toBeInTheDocument();
});
});
10. Styles CSS
/ SearchApp.css /
.search-app {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}
.filters {
display: flex;
gap: 16px;
margin: 24px 0;
}
.search-input,
.category-select {
padding: 12px 16px;
border: 2px solid #e2e8f0;
border-radius: 8px;
font-size: 16px;
transition: border-color 0.2s;
}
.search-input {
flex: 1;
}
.search-input:focus,
.category-select:focus {
outline: none;
border-color: #3b82f6;
}
.articles-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 24px;
margin-top: 24px;
}
.article-card {
padding: 24px;
border: 1px solid #e2e8f0;
border-radius: 12px;
background: white;
transition: transform 0.2s, box-shadow 0.2s;
}
.article-card:hover {
transform: translateY(-4px);
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.1);
}
.article-card h2 {
margin: 0 0 12px 0;
color: #1a202c;
}
.meta {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 16px;
}
.category {
padding: 4px 12px;
background: #e0e7ff;
color: #4c51bf;
border-radius: 12px;
font-size: 14px;
}
.tags {
display: flex;
gap: 8px;
}
.tag {
color: #718096;
font-size: 14px;
}
.loading,
.no-results {
text-align: center;
padding: 48px;
color: #718096;
font-size: 18px;
}
Meilleures pratiques 2025
handleClick, isLoading, hasErrorConclusion
Vous maîtrisez maintenant les trois piliers de React :