Données réactives — useState
useState est le Hook le plus important de React. Dans ce chapitre, vous comprendrez le mécanisme de gestion d'état de React et implémenterez le filtrage par catégorie.
En quoi la « réactivité » de React est-elle différente de celle de Vue3 ?
Vue3 utilise ref/reactive pour créer des données réactives ; lorsque les données changent, la vue se met à jour automatiquement.
L'approche de React est différente :Lorsque vous modifiez l'état, React réexécute la fonction du composant avec le nouvel état et effectue le rendu d'une nouvelle vue。
C'est l'origine du nom de React — les composants « réagissent » aux changements d'état.
Utilisation de base de useState
useStateC'est le Hook le plus élémentaire de React, utilisé pour ajouter de l'état aux composants fonctionnels.
Exemple
import { useState } from 'react'
function Counter() {
// useState(valeur initiale) renvoie un tableau : [valeur actuelle, fonction de mise à jour]
const [count, setCount] = useState(0)
function handleClick() {
setCount(count + 1) // Mettre à jour l'état, React re-rendra automatiquement le composant
}
return (
<div>
<p>Nombre de clics :{count}</p>
<button onClick={handleClick}>+1</button>
</div>
)
}
Chaque fois que le bouton est cliqué,setCountla valeur de count est mise à jour, React réexécute la fonction Counter avec la nouvelle valeur, renvoie un nouveau JSX, et la page se met à jour automatiquement.
Convention React : les noms d'état utilisent
[something, setSomething]déstructuration. Les fonctions commençant par set sont une pratique de nommage conventionnelle, pas une obligation stricte, mais toute la communauté l'écrit ainsi.
Pourquoi ne pouvons-nous pas modifier directement l'état ?
Dans React,l'état est en lecture seule. Il doit être mis à jour via une fonction setter ; vous ne pouvez pas le modifier directement.
Exemple
function ArticleManager() {
const [title, setTitle] = useState('Default Title')
// Mauvaise façon : modification directe
// title = 'New Title' // React ne sait pas que vous l'avez modifié, donc il ne se re-rendra pas
// Bonne façon : mise à jour via le setter
function updateTitle() {
setTitle('New Title') // React sait que l'état a changé et va se re-rendre
}
return (
<div>
<p>{title}</p>
<button onClick={updateTitle}>Mettre à jour le titre</button>
</div>
)
}
Derrière cela se trouve leprincipe d'immuabilitéde React : ne modifiez pas les anciennes données, mais remplacez-les par de nouvelles données.
La bonne façon de mettre à jour les tableaux et les objets
Pour les tableaux et les objets, vous ne pouvez pas directement utiliser push, splice ou modifier les propriétés ; vous devez créer une nouvelle copie.
Exemple
function TodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: 'Learn React', done: false },
{ id: 2, text: 'Write Blog', done: vrai }
])
// Ajouter un élément : utiliser l'opérateur de décomposition sur l'ancien tableau + le nouvel élément
function addTodo(text) {
const newTodo = { id: Date.now(), text, done: false }
setTodos([...todos, newTodo]) // Utiliser l'opérateur de décomposition pour créer un nouveau tableau
}
// Modifier un élément : itérer avec map, trouver celui à modifier, retourner un nouvel objet
function toggleTodo(id) {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, done: !todo.done } : todo
))
}
// Supprimer un élément : utiliser filter pour retirer celui non désiré
function removeTodo(id) {
setTodos(todos.filter(todo => todo.id !== id))
}
return (
<ul>
{todos.map(todo => (
<li key={todo.id} onClick={() => toggleTodo(todo.id)}>
{todo.done ? <s>{todo.text}</s> : todo.text}
</li>
))}
</ul>
)
}
| Opération | Mauvaise façon | Bonne façon |
|---|---|---|
| Add | arr.push(item) | setArr([...arr, item]) |
| Modifier | arr[0] = newVal | setArr(arr.map(el => el.id === id ? ... : el)) |
| Delete | arr.splice(i, 1) | setArr(arr.filter(el => el.id !== id)) |
| Modifier les propriétés d'un objet | obj.name = 'xxx' | setObj({ ...obj, name: 'xxx' }) |
Principe fondamental : remplacez toujours l'ancien par un nouveau tableau/nouvel objet. L'opérateur de décomposition
...et les méthodes de tableau que vous avez apprises (map, filter) sont les meilleurs partenaires pour les mises à jour immuables.
useMemo — Valeurs calculées dérivées
useMemoUtilisé pour mettre en cache les résultats de calcul. Lorsque les dépendances n'ont pas changé, ignorez le recalcul pour éviter une surcharge de performance inutile.
Exemple
function ArticleFilter() {
const [articles] = useState([
{ id: 1, title: 'Débuter avec React', category: 'React' },
{ id: 2, title: 'Promise expliquée', category: 'JavaScript' },
{ id: 3, title: 'Mise en page en grille', category: 'CSS' },
{ id: 4, title: 'React Hooks', category: 'React' }
])
const [activeCategory, setActiveCategory] = useState('Toutes')
// useMemo met en cache le résultat du filtrage ; il ne le recalcule que lorsque les dépendances changent
const filteredArticles = useMemo(() => {
console.log('Recalculer les résultats du filtre') // Vérifier l'effet de mise en cache
if (activeCategory === 'Tous') return articles
return articles.filter(a => a.category === activeCategory)
}, [articles, activeCategory]) // Tableau de dépendances
// Utiliser useMemo pour calculer des statistiques
const totalCount = useMemo(() => articles.length, [articles])
const filteredCount = useMemo(() => filteredArticles.length, [filteredArticles])
return (
<div>
<p>commun{totalCount}articles, actuellement{filteredCount}article</p>
<button onClick={() => setActiveCategory('Tous')}>Tous</button>
<button onClick={() => setActiveCategory('React')}>React</button>
<button onClick={() => setActiveCategory('JavaScript')}>JavaScript</button>
<button onClick={() => setActiveCategory('CSS')}>CSS</button>
{filteredArticles.map(a => (
<div key={a.id}><h3>{a.title}</h3><span>{a.category}</span></div>
))}
</div>
)
}
Le tableau de dépendances de useMemo(fn, deps) doit être rempli complètement : toutes les variables internes du composant utilisées dans la fonction doivent être déclarées dans le tableau. React décide s'il faut recalculer via une comparaison superficielle.
Exercice pratique : Ajouter un filtrage par catégorie au blog
Exemple
import { useState, useMemo } from 'react'
import './App.css'
function App() {
const [articles] = useState([
{ id: 1, title: 'Le guide complet pour démarrer avec React', summary: 'Apprendre React de zéro', category: 'React', date: '2024-05-10' },
{ id: 2, title: 'Programmation asynchrone en JS expliquée', summary: 'Comprendre Promise et async/await', category: 'JavaScript', date: '2024-05-08' },
{ id: 3, title: 'CSS Grid Layout en action', summary: 'Mise en œuvre de mises en page responsives avec Grid', category: 'CSS', date: '2024-05-05' },
{ id: 4, title: 'React Hooks en profondeur', summary: 'Compréhension approfondie de useState et useEffect', category: 'React', date: '2024-05-03' },
{ id: 5, title: 'Le guide complet de Flexbox', summary: 'Apprendre Flex Layout en un article', category: 'CSS', date: '2024-05-01' },
])
const [activeCategory, setActiveCategory] = useState('Toutes')
// Extraire toutes les catégories (dédoublonner)
const categories = useMemo(() => {
const cats = articles.map(a => a.category)
return ['Tous', ...new Set(cats)]
}, [articles])
// Filtrer les articles par catégorie
const filteredArticles = useMemo(() => {
if (activeCategory === 'Tous') return articles
return articles.filter(a => a.category === activeCategory)
}, [articles, activeCategory])
return (
<div className="app">
<header className="navbar">
<h1 className="logo">Blog EXEMPLE</h1>
<nav>
<a href="/">Accueil</a>
<a href="#">À propos</a>
</nav>
</header>
<main className="container">
<h2 className="section-title">Derniers articles</h2>
{/* Groupe de boutons de filtre par catégorie */}
<div className="category-bar">
{categories.map(cat => (
<button
key={cat}
className={activeCategory === cat ? 'active' : ''}
onClick={() => setActiveCategory(cat)}
>
{cat}
</button>
))}
</div>
<p className="result-info">common{filteredArticles.length}article</p>
{filteredArticles.length === 0 ? (
<p className="empty-tip">Aucun article dans cette catégorie pour le moment</p>
) : (
<div className="article-grid">
{filteredArticles.map(article => (
<div key={article.id} className="article-card">
<div className="card-content">
<span className="card-category">{article.category}</span>
<h3>{article.title}</h3>
<p>{article.summary}</p>
<span className="card-date">{article.date}</span>
</div>
</div>
))}
</div>
)}
</main>
<footer className="footer">
<p>© 2024 EXEMPLE Blog. Propulsé par React.</p>
</footer>
</div>
)
}
export default App
Exemple
.category-bar {
display: flex;
gap: 10px;
margin-bottom: 16px;
flex-wrap: wrap;
}
.category-bar button {
padding: 6px 16px;
border: 1px solid #ddd;
border-radius: 20px;
background: #fff;
cursor: pointer;
font-size: 14px;
transition: all 0.2s;
}
.category-bar button.active {
background: #1976d2;
color: #fff;
border-color: #1976d2;
}
.category-bar button:hover {
border-color: #1976d2;
}
.result-info {
color: #999;
font-size: 14px;
margin-bottom: 16px;
}
Résumé du chapitre
Dans ce chapitre, vous avez maîtrisé les trois concepts fondamentaux de la gestion d'état React : useState pour créer et mettre à jour l'état, le principe de mise à jour immuable (opérateur de propagation + map/filter), et useMemo pour mettre en cache les valeurs calculées dérivées.
Grâce à la fonctionnalité de filtrage par catégorie, vous avez découvert le modèle piloté par les données de React : « setState → re-rendu → nouvelle vue ».
Autres extensions