Cycle de vie des composants

Mounting, updating, unmounting et équivalents avec les Hooks

Vue d'ensemble

Cycle de vie d'un composant React

Chaque composant React passe par trois grandes phases :

PhaseDescription
MountingLe composant est créé et inséré dans le DOM
UpdatingLes props ou le state changent → re-render
UnmountingLe composant est retiré du DOM

Méthodes de classe vs Hooks

Classe (legacy)Hook équivalent
constructor()useState(valeurInitiale)
componentDidMount()useEffect(() => { ... }, [])
componentDidUpdate(prevProps, prevState)useEffect(() => { ... }, [dep])
componentWillUnmount()useEffect(() => { return () => { ... } }, [])
shouldComponentUpdate()React.memo / useMemo
componentDidCatch()Error Boundary (classe uniquement)

Mounting

Phase de Mounting

Le composant est initialisé et rendu pour la première fois dans le DOM.

Avec les classes

class MonComposant extends Component {
  constructor(props) {
    super(props);
    this.state = { data: null }; // 1. Initialisation du state
  }

  componentDidMount() {
    // 2. Appelé APRÈS le premier render (DOM disponible)
    fetch('/api/data')
      .then(res => res.json())
      .then(data => this.setState({ data }));
  }

  render() {
    return <div>{this.state.data}</div>;
  }
}

Avec les Hooks

function MonComposant() {
  const [data, setData] = useState(null); // Initialisation

  useEffect(() => {
    // Équivalent de componentDidMount (tableau vide = exécution unique)
    fetch('/api/data')
      .then(res => res.json())
      .then(data => setData(data));
  }, []); // [] = seulement au montage

  return <div>{data}</div>;
}

Ordre d'exécution au montage

  1. La fonction du composant s'exécute (rendu initial)
  2. React met à jour le DOM
  3. Les effets (useEffect) s'exécutent

Updating

Phase de Updating

Le composant se re-rend quand ses props ou son state changent.

Avec les classes

componentDidUpdate(prevProps, prevState) {
  // Appelé après chaque re-render
  if (prevProps.userId !== this.props.userId) {
    // Les props ont changé → réagir
    this.fetchUser(this.props.userId);
  }

  if (prevState.count !== this.state.count) {
    // Le state a changé → réagir
    console.log("Count changé :", this.state.count);
  }
}

Avec les Hooks

// Réagir aux changements de props ou state
useEffect(() => {
  fetchUser(userId);
}, [userId]); // S'exécute quand userId change

useEffect(() => {
  console.log("Count changé :", count);
}, [count]);

Optimiser les re-renders

// Éviter les re-renders inutiles du composant enfant
const EnfantMemo = React.memo(function Enfant({ nom }) {
  return <p>{nom}</p>;
});

// shouldComponentUpdate (classe)
shouldComponentUpdate(nextProps, nextState) {
  return nextProps.value !== this.props.value;
}

Ordre d'exécution lors d'une mise à jour

  1. Le state ou les props changent
  2. React re-exécute la fonction du composant
  3. React met à jour le DOM (diff)
  4. Les effets dont les dépendances ont changé s'exécutent (cleanup de l'ancien effet d'abord)

Unmounting

Phase de Unmounting

Le composant est retiré du DOM. C'est le moment de nettoyer les effets de bord : timers, abonnements, listeners...

Avec les classes

class Timer extends Component {
  componentDidMount() {
    this.interval = setInterval(() => {
      this.setState(prev => ({ count: prev.count + 1 }));
    }, 1000);
  }

  componentWillUnmount() {
    // Nettoyage obligatoire pour éviter les memory leaks
    clearInterval(this.interval);
  }

  render() { return <p>{this.state.count}</p>; }
}

Avec les Hooks (fonction de cleanup)

function Timer() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      setCount(prev => prev + 1);
    }, 1000);

    // La fonction retournée = cleanup (unmount OU avant le prochain effet)
    return () => clearInterval(interval);
  }, []);

  return <p>{count}</p>;
}

Cas d'usage typiques du cleanup

useEffect(() => {
  const controller = new AbortController();

  fetch('/api/data', { signal: controller.signal })
    .then(res => res.json())
    .then(setData)
    .catch(err => {
      if (err.name !== 'AbortError') console.error(err);
    });

  return () => controller.abort(); // Annule la requête si démontage
}, []);