Pourquoi se former aux Hooks de React ?

2021-06-17
Pourquoi-se-former-aux-Hooks-de-React
Si vous voulez simplifier votre travail sur vos projets React, cet article a été écrit spécialement pour vous. L’École O’clock lance ses formations pros. Alors, pour l’occasion, on a filé le micro à Tony et Noé, deux de nos formateurs experts en React. 

C’est quoi les hooks ?

Définition :

Les Hooks sont une nouveauté de React 16.8. Ils permettent de bénéficier d’un état local et d’autres fonctionnalités de React sans avoir à écrire de classes.

Documentation de React

Un hook custom nous permet de composer des fonctionnalités complexes en React, en les encapsulant dans des fonctions. Il est ainsi possible de réutiliser de la logique autre que d’affichage entre plusieurs composants React.

Est-ce que je devrais apprendre les hooks ?

Les hooks sont la nouvelle méthode privilégiée pour accéder aux fonctionnalités de React. Auparavant, dans React, il fallait passer assez vite par des composants sous forme de classe dès que l’on souhaitait accéder à des fonctionnalités telles que :

  • utilisation de références
  • utilisation d’état local
  • utilisation de méthodes de cycle de vie

Toutefois, les développeurs de React ont indiqué qu’ils ne souhaitent pas supprimer les classes par souci de rétro-compatibilité. En d’autres termes, il sera toujours possible d’écrire du code React sous forme de classes, y compris pour les nouvelles fonctionnalités à venir.

Dans cet article, nous exposerons l’avantage des hooks, les problèmes qu’ils visent à résoudre et donnerons quelques exemples. Vous serez alors en mesure de décider si vous souhaitez vous lancer dans cet apprentissage.

Pourquoi les hooks ?

La documentation de React nous décrit les différentes raisons qui ont mené à la création des hooks.

Introduction aux hooks

Les problèmes de l’approche objet

La doc de React contient une phrase assez intéressante :

Les classes sont déroutantes pour les gens comme pour les machines

Les classes ont une syntaxe assez lourde. Pour chaque composant React, il faut déclarer :

  • la classe qui hérite de React.Component
  • le constructeur si l’on souhaite utiliser un state
  • la méthode render.

Il faut également gérer différents soucis de contexte : this qui devient undefined, besoin de .bind(this) les fonctions…

De plus, on ne tire même pas pleinement partie de la programmation objet ! Avez-vous déjà essayé de faire hériter un composant d’un autre ? Ce n’est pas très facile et à vrai dire totalement déconseillé par React

Les soucis propres à React

Composants trop gros

Soit le code suivant :

jsx class MonComposant extends React.component {
  constructor(props) {
    super(props);

    this.state = {
      count: 0,
      autreValeur: 'blabla',
    }

    this.handleClick = this.handleClick.bind(this)
  }

  componentDidMount() {
    // on change le titre de la page lorsque le state change.
    document.title = `le bouton à été cliqué ${this.state.count} fois`;
  }

  componentDidUpdate() {
    // on change le titre de la page lorsque le state change.
    document.title = `le bouton à été cliqué ${this.state.count} fois`;
  }

  handleClick() {
    this.setState({
      count: this.state.count + 1
    }));
  }

  render() {
    return (

) } }

On a beaucoup de code pour au final pas grand chose : l’initialisation du state est assez verbeuse, il ne faut pas oublier le .bind(this) de la fonction handleClick. On constate que le code entre componentDidMount et componentDidUpdate est également dupliqué.

Fausse hiérarchie et « Wrapper Hell »

Fausse hiérarchie et Wrapper Hell

Si l’on souhaite mutualiser certaines fonctionnalités, on doit utiliser des patterns qui complexifient le code, comme le fait d’utiliser un composant wrapper.

Un composant est censé afficher uniquement de l’interface, il est donc dommage de l’utiliser pour partager de la logique.

De plus, l’arbre de nos composants devient très difficile à lire.

Ces problèmes découlent principalement du fait que l’on utilise des classes. En organisant notre code autour de fonctions les plus simples possibles, il sera plus aisé de composer nos fonctionnalités.

Programmation fonctionnelle

Avant de rentrer dans le détail des hooks, il nous semble intéressant de présenter le paradigme de la programmation fonctionnelle. Le sujet est vaste et l’idée ici n’est pas de donner un cours théorique, mais plutôt d’identifier les principes les plus importants.

En programmation fonctionnelle, on s’interdit tout effet secondaire.

  • Une fonction n’a pas d’effets secondaires (également appelés effets de bord) lorsque :
    1. elle n’utilise que ses paramètres
    2. elle ne modifie que sa valeur de retour
  • En conséquence, elle renvoie toujours le même résultat si elle reçoit des paramètres identiques.

Une fonction qui respecte ces règles est qualifiée de « fonction pure ». L’idée étant de rendre le comportement des éléments de notre programme aussi prévisible que possible et plus facile à tester unitairement.

Lorsqu’une fonction impacte le monde extérieur, on parlera d’effet de bord (en anglais side effect).

La Composition (programmation fonctionnelle) vs Héritage (programmation orientée objet)

On peut simplifier la différence entre les deux ainsi :

L’héritage = définir nos types par rapport à ce qu’ils sont.

La composition = définir nos types par rapport à ce qu’ils font.

Exemple :

Considérons les types suivants :

javascript Animal
  .mange()

Chat // hérite de animal
  .miaule()

Chien // hérite de animal
  .aboie()
javascript Robot
  .roule()

RobotTueur // hérite de robot
  .tue()

RobotNettoyeur // hérite de robot
  .nettoie()

Jusqu’ici, tout va bien…

Voilà une structure d’héritage assez classique. Mais que se passe-t-il si le client demande :

Maintenant je voudrais un Chat-Robot, qui puisse miauler et rouler. Par contre, pas besoin qu’il mange ! Gloups !

La composition à la rescousse

Définissons ce que FONT nos types au lieu de ce qu’ils SONT

javascript Chien = choseQuiAboie + choseQuiMange
Chat = choseQuiMiaule + choseQuiMange
RobotTueur = choseQuiTue + choseQuiRoule
RobotNettoyeur = choseQuiNettoie + choseQuiRoule
ChatRobot = choseQuiMiaule + choseQuiRoule

En termes de code – 1

Définissons les « actions »

javascript const choseQuiMiaule = (state) => ({
  miauler: () => console.log('Miaou, je suis ' + state.name)
})

const choseQuiRoule = (state) => ({
  rouler: () => state.position = state.position + state.speed
})

En termes de code – 2

Et regroupons le tout

javascript const ChatRobot = (name) => {
   let state = {
     name,
     speed: 100,
     position: 0
   }

   return {
     ...choseQuiMiaule(state),
     ...choseQuiRoule(state)
   };
 }

Ne vous inquiétez pas si le code vous dérange, c’est surtout le principe qui est important.

Et React dans tout ça ?

React est déjà un moteur de composition !

composition… composant…

Composition...Composant...

Exemple de composition

jsx const Popup = (props) => (

{props.title}

{props.message}

) const PopupDeBienvenue = () => ( )

En JSX, nous savons déjà composer des composants entre eux, pour n’utiliser que ce dont nous avons besoin.

Lorsque des données doivent transiter : on utilise les props.

Le fond du problème

Nous savons donc composer des composants.

Mais nous ne savons pas composer au sein d’un composant !

C’est justement ce que nous proposent les hooks !

Les hooks nous permettent de créer des abstractions sans affecter l’arbre des composants.

Exemples de hooks

Imaginons que l’on souhaite convertir un composant Class en composant fonction, utilisant les hooks.

Soit le code suivant :

typescript import React from 'react';

class MonComposant extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0,
    }
  }

  updateTitle(){
      const { count } = this.state;
      document.title = `le bouton à été cliqué ${count} fois`;
  }

  componentDidMount() {
    // on change le titre de la page lorsque le state change.
    this.updateTitle();
  }

  componentDidUpdate() {
    // on change le titre de la page lorsque le state change.
    this.updateTitle();
  }

  handleClick() {
    // On augmente le compteur de 1 au clic.
    const { state } = this;
    this.setState({
      ...state,
      count: state.count + 1
    });
  }

  render() {
    const { count } = this.state;
    return (

Vous avez pour le moment cliqué { count } fois

) } } export default MonComposant;

En analysant ce composant, on peut découper ses différentes responsabilités ainsi:

  • Créer le bouton et le texte (affichage)
  • Réagir au clic sur le bouton (event)
  • Maintenir un état (state) pour la valeur du compteur
  • Agir à l’extérieur du composant (side effect) pour modifier le titre du document

Les 2 premiers points ne nécessitent pas l’utilisation de hooks, un simple composant de type fonction permet de répondre à ces besoins.

Transformons donc notre Class en composant fonction:

jsx import React from 'react';

const MonComposant = () => {
    // Je crée la fonction pour réagir au clic
    const handleClick = () => {
        console.log('click');
        // Dans l'idéal, j'aimerais ici modifier un state
    }

    // Je return ce que je souhaite afficher
    return (

Vous avez pour le moment cliqué X fois

); }; export default MonComposant;

À ce stade, même si nous avons récupéré notre affichage, il manque toujours le plus important : réagir aux actions de l’utilisateur.

Ce qui nous amène aux deux derniers points des responsabilités de notre composant :

  • Maintenir un état (state) pour la valeur du compteur.
  • Agir à l’extérieur du composant (side effect) pour modifier le titre du document.

Pour chacun de ces besoins, React nous fournit un hook adapté.

useState

Le hook useState va nous permettre d’ajouter un système de gestion d’état à notre composant.

TOUS les hooks React ont comme point commun :

  • d’être des fonctions
  • qui commencent par use...
  • et doivent être utilisés à la racine de notre composant

Ce hook en particulier s’utilise ainsi :

typescript import React, { useState } from 'react';

const MonComposant = () => {
    // Je crée un state (count) et une méthode pour le modifier (setCount)
    // Mon state démarre par défaut avec la valeur que j'ai donné au hook en argument (0)
    const [count, setCount] = useState(0);

    
    const handleClick = () => {
        // J'utilise setCount pour modifier le state
        setCount(count + 1);
    }

    // J'utilise count pour afficher mon state
    return (

Vous avez pour le moment cliqué {count} fois

); }; export default MonComposant;

Contrairement au state de Class, avec les hooks:

  • mon state n’est pas forcément un objet
  • mon state ne s’appelle pas forcément « state »
  • je ne suis pas limité à un seul state, je peux en créer autant que nécessaire

Il existe d’autres différences, mais l’idée est pour le moment de découvrir les principales.

Il nous reste une problématique de taille à adresser : les composants Class permettaient également de déclencher des actions à différents moments de leur cycle de vie (à la création, ou à la mise à jour du composant).

useEffect

Pour remplacer les méthodes componentDidMount et componentDidUpdate de notre composant original, portons notre attention sur le hook useEffect. Comme son nom l’indique, il est idéal pour gérer les « effets de bord » (side effects).

Dans notre exemple, le composant doit modifier le titre du document dès qu’il est monté dans le DOM la première fois ET à chaque nouveau rendu.

jsx import React, {useState, useEffect} from 'react';

const MonComposant = () => {
    //...

    // Je donne en premier (et seul) argument a useEffect une fonction
    // Elle sera exécutée au premier ET à tous les prochains rendus de mon composant
    useEffect(() => {
        document.title = `Vous avez cliqué ${count} fois`;
    });
    
    // ...

    return (

Vous avez pour le moment cliqué {count} fois

); }; export default MonComposant;

Comme on peut le constater, pas besoin de spécifier 2 fois le fait que l’on souhaite modifier le title.

Le hook useEffect, dans son utilisation la plus simple (avec un seul argument) remplace les 2 méthodes de Class qui nous intéressaient (didMount et didUpdate).

Il reste là aussi bien des choses à éclaircir sur ce hook, notamment :

  • Comment faire pour n’agir qu’au premier render (équivalent de didMount uniquement)
  • Comment faire pour réagir à la modification d’une seule donnée
  • ou pour agir juste avant que notre composant soit retiré du DOM (willUnmount)

Conclusion

Comme nous l’avons vu, employer les hooks permet d’alléger notre code, de le rendre plus facile à maintenir et de gagner en lisibilité. On s’affranchit des classes et le code devient plus concis et nos applications plus aisées à maintenir.

Toutefois, le choix de React d’adopter cette architecture de programmation fonctionnelle s’inscrit dans une dynamique plus générale.

Il y a encore une dizaine d’années, la majorité du code Javascript s’articulait autour d’une approche orientée objet, mais ces dernières années, nous assistons à une véritable explosion de la programmation fonctionnelle.

De plus en plus de frameworks et librairies s’appuient sur ce paradigme pour produire du code plus testable, robuste et composable.

La capacité de l’équipe de React à revenir sur l’approche objet et proposer l’alternative des hooks tout en assurant la rétro-compatibilité avec les classes, montre également une grande maturité et rassure énormément sur le futur de cette librairie.

La bonne nouvelle, c’est que vous n’avez pas besoin en soi d’apprendre les hooks : tout le code que vous savez déjà faire en React reste valide. Toutefois, si vous trouvez votre code mal architecturé et la syntaxe des composants sous forme de classe difficile, il serait dommage ne pas sauter le pas !

Alors, vous avez accroché avec les Hooks de Réact ? Si vous souhaitez avoir plus d’infos sur ces petites perles d’optimisations, c’est ici que ça se passe. 👇