Effets de bord et récupération de données — useEffect

Dans ce chapitre, vous apprendrez le Hook d'effet de bord le plus important de React — useEffect, et utiliserez fetch pour charger les données du blog depuis un fichier JSON au bon moment.


Qu'est-ce qu'un effet de bord ?

En React,un effet de borddésigne des opérations sans rapport avec le rendu :

  • Demander des données au serveur
  • Manipuler localStorage
  • Définir des minuteurs avec setInterval
  • Modifier manuellement le DOM
  • S'abonner à des événements externes

Les effets de bord ne peuvent pas être exécutés directement dans le corps de la fonction du composant (ce qui les ferait s'exécuter à chaque rendu) ; ils doivent être placés dansuseEffect.


Utilisation de base de useEffect

Exemple

import { useState, useEffect } from 'react'

function HomePage() {
  const [message, setMessage] = useState('Loading...')

  // useEffect(fn, deps) : exécute l'effet secondaire après le rendu du composant
  useEffect(() => {
    console.log('Le composant a été monté dans le DOM ; les effets secondaires peuvent maintenant être exécutés.')
    setMessage('Chargement des données terminé !')
  }, [])  // Tableau de dépendances vide = s'exécute une seule fois après le rendu initial

  return <p>{message}</p>
}

Trois façons d'écrire le tableau de dépendances

SyntaxeMoment d'exécutionCas d'utilisation
useEffect(fn)S'exécute après chaque renduRarement utilisé ; sujet aux boucles infinies
useEffect(fn, [])S'exécute une seule fois après le rendu initialChargement des données, initialisation de bibliothèques tierces
useEffect(fn, [dep])S'exécute après le rendu initial + lorsque dep changeÉcouter les changements de données spécifiques et exécuter les opérations correspondantes

Différence entre omettre le tableau de dépendances et un tableau vide :useEffect(fn)S'exécute après chaque rendu ;useEffect(fn, [])Il ne s'exécute qu'une seule fois après le rendu initial. La première peut facilement provoquer des boucles infinies (setState dans l'effet → déclenche un rendu → l'effet s'exécute à nouveau → ...), il n'est donc presque jamais utilisé.


Chargement de données JSON avec fetch

Tout comme Vue3, nous plaçons les données de l'article dans lepublic/posts.jsonfichier.

Étape 1 : Créer le fichier de données

Exemple

// Chemin du fichier : public/posts.json
[
  {
    "id": 1,
"title": "Le guide complet pour bien démarrer avec React",
"summary": "Apprenez les Hooks React à partir de zéro, en couvrant les concepts fondamentaux tels que useState et useEffect.",
"content": "<h2>Pourquoi apprendre React ?</h2><p>React est l'un des frameworks front-end les plus populaires...</p>",
    "category": "React",
    "date": "2024-05-10"
  },
  {
    "id": 2,
"title": "Programmation asynchrone en JavaScript expliquée",
"summary": "Comprenez Promise, async/await, la boucle d’événements et la file des microtâches en un seul article.",
"content": "<h2>Qu’est-ce que l’asynchrone ?</h2><p>JS est monothread...</p>",
    "category": "JavaScript",
    "date": "2024-05-08"
  },
  {
    "id": 3,
"title": "CSS Grid Layout en pratique",
"summary": "Implémentez facilement des mises en page responsives complexes avec CSS Grid.",
"content": "<h2>Premiers pas avec Grid</h2><p>Grid est un système de mise en page bidimensionnel...</p>",
    "category": "CSS",
    "date": "2024-05-05"
  }
]

Étape 2 : Charger les données dans useEffect

Exemple

// Chemin du fichier : src/pages/HomePage.jsx
import { useState, useEffect, useMemo } from 'react'
import BlogCard from '../components/BlogCard'
import CategoryFilter from '../components/CategoryFilter'

function HomePage() {
  const [articles, setArticles] = useState([])   // Données des articles
  const [isLoading, setIsLoading] = useState(true) // État de chargement
  const [error, setError] = useState(null)        // Message d'erreur
  const [activeCategory, setActiveCategory] = useState('Toutes')

  // useEffect charge les données : tableau de dépendances vide = ne s'exécute qu'une seule fois après le rendu initial
  useEffect(() => {
    let cancelled = false  // Indicateur pour empêcher setState après le démontage du composant

    async function fetchPosts() {
      setIsLoading(true)
      setError(null)
      try {
        const res = await fetch('/posts.json')
        if (!res.ok) throw new Erreur(`HTTP ${res.status}`)
        const data = await res.json()
        if (!cancelled) {
          setArticles(data)
        }
      } catch (err) {
        if (!cancelled) {
          setError(err.message)
        }
      } finally {
        if (!cancelled) {
          setIsLoading(false)
        }
      }
    }

    fetchPosts()

    // Fonction de nettoyage : définir cancelled sur true lorsque le composant est démonté
    return () => { cancelled = true }
  }, [])

  const categories = useMemo(() => {
    return ['Toutes', ...new Set(articles.map(a => a.category))]
  }, [articles])

  const filteredArticles = useMemo(() => {
    if (activeCategory === 'Tous') return articles
    return articles.filter(a => a.category === activeCategory)
  }, [articles, activeCategory])

  return (
    <div>
      <h2 className="section-title">Derniers articles</h2>

      {/* Chargement */}
      {isLoading && <p className="status-msg">Chargement, veuillez patienter...</p>}

      {/* Erreur de chargement */}
      {error && (
        <div className="status-msg error">
          <p>Échec du chargement :{error}</p>
          <button onClick={() => window.location.reload()}>Réessayer</button>
        </div>
      )}

      {/* Chargement des données terminé */}
      {!isLoading && !error && (
        <>
          <CategoryFilter
            categories={categories}
            activeCategory={activeCategory}
            onCategoryChange={setActiveCategory}
          />
          <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 => (
                <BlogCard key={article.id} {...article} />
              ))}
            </div>
          )}
        </>
      )}
    </div>
  )
}

export default HomePage

cancelledL'utilisation d'un drapeau est un modèle courant dans React. Lorsque fetch est toujours en cours et que l'utilisateur quitte la page (le composant est démonté), le setState suivant signalera un avertissement d'erreur. Le définir dans la fonction de nettoyagecancelled = truepermet d'éviter setState après le démontage.


Fonction de nettoyage

La fonction de rappel de useEffect peut renvoyer une fonction, appelée lafonction de nettoyage。

Il s'exécute à deux moments :Lorsque le composant est démontéetAvant l'exécution de l'effet suivant。

Exemple

useEffect(() => {
  // Effet secondaire : s'abonner aux événements
  const handleScroll = () => console.log('Scrolled')
  window.addEventListener('scroll', handleScroll)

  // Retourner la fonction de nettoyage
  return () => {
    // Supprimer automatiquement l'écouteur d'événement lorsque le composant est démonté pour éviter les fuites de mémoire
    window.removeEventListener('scroll', handleScroll)
  }
}, [])

useEffect(() => {
  // Effet secondaire : définir un minuteur
  const timer = setInterval(() => {
    console.log('Executes every second')
  }, 1000)

  return () => clearInterval(timer)  // Arrêter le minuteur
}, [])
Effet secondaireNécessite un nettoyage ?Méthode de nettoyage
requête fetchOuiUtiliser un indicateur cancelled ou AbortController
Écouteur d'événementOuiremoveEventListener
MinuteurOuiclearInterval / clearTimeout
Modifier le DOM puis restaurerOuiRestaurer l'état d'origine lors du nettoyage
Écritures dans localStoragenonAucun nettoyage nécessaire

Quatre états pour la gestion des requêtes asynchrones

Toute requête de données doit couvrir les quatre états UI suivants :

StateConditionUI
ChargementisLoading === trueAnimation de chargement, écran squelette
SuccèsLes données ont été retournéesAfficher le contenu normalement
Failureerror !== nullMessage d'erreur + bouton de nouvelle tentative
Données videsLes données sont un tableau videMessage d'état vide

Comparaison avec le cycle de vie de Vue3

Vue3ReactDescription
onMounteduseEffect(fn, [])S'exécute après le rendu initial
watch(keyword, fn)useEffect(fn, [keyword])Réagit à des changements de données spécifiques
onBeforeUnmountuseEffect 的 cleanupNettoyage avant la destruction du composant
Aucun équivalent directuseEffect(() => { fn; return cleanup }, [dep])Lorsque dep change, exécutez d'abord le nettoyage, puis fn

Résumé du chapitre

Dans ce chapitre, vous avez maîtrisé l'utilisation essentielle de useEffect : les trois façons d'écrire le tableau de dépendances, le chargement asynchrone des données avec fetch, la gestion des quatre états de l'interface, et le rôle de la fonction de nettoyage.

Désormais, les données du blog sont chargées dynamiquement depuis un fichier JSON externe, elles ne sont plus codées en dur en JS.

Autres extensions