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

// Importer useState par déstructuration depuis react
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

import { useState } from 'react'

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

import { useState } from 'react'

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érationMauvaise façonBonne façon
Addarr.push(item)setArr([...arr, item])
Modifierarr[0] = newValsetArr(arr.map(el => el.id === id ? ... : el))
Deletearr.splice(i, 1)setArr(arr.filter(el => el.id !== id))
Modifier les propriétés d'un objetobj.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

import { useState, useMemo } from 'react'

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

// Chemin du fichier : src/App.jsx
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

/* Chemin de fichier : ajouter les styles des boutons de filtre de catégorie à src/App.css */
.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