Article original : React Optimization Techniques to Help You Write More Performant Code

L'optimisation des performances est un aspect critique du développement d'applications web. Les utilisateurs s'attendent à ce que les applications se chargent rapidement et répondent à leurs interactions de manière fluide.

Dans l'écosystème React, les techniques d'optimisation des performances peuvent améliorer considérablement l'expérience utilisateur en réduisant les temps de chargement et en améliorant la réactivité.

Dans cet article, nous allons discuter de huit techniques efficaces pour optimiser les performances de votre application React.

Table des matières

  1. Pourquoi l'optimisation des performances est importante
  2. Visualisation de liste
  3. Chargement paresseux des images
  4. Mémoisation
  5. Throttling et Debouncing des événements
  6. Division de code
  7. Fragments React
  8. Web Workers
  9. Hook UseTransition
  10. Conclusion

Pourquoi l'optimisation des performances est importante

Optimiser les performances de votre application React est crucial pour plusieurs raisons :

  • Meilleure expérience utilisateur : Une application lente ou qui lag peut entraîner une mauvaise expérience utilisateur, impactant négativement votre entreprise. Les utilisateurs s'attendent à des interactions rapides et réactives, et l'optimisation des performances aide à fournir cela.
  • SEO amélioré : Les moteurs de recherche comme Google prennent en compte les temps de chargement des pages et les performances globales lors du classement des sites web. Une application bien optimisée sera mieux classée dans les résultats de recherche, la rendant plus visible pour les utilisateurs potentiels.
  • Taux de rebond réduits : Si votre application met trop de temps à se charger ou à répondre, les utilisateurs partiront probablement et ne reviendront jamais. En optimisant les performances, vous pouvez réduire les taux de rebond et augmenter l'engagement.
  • Économies de coûts : Une application performante nécessite moins de ressources (comme des serveurs et de la mémoire) pour gérer la même charge de travail. Cela signifie des coûts d'hébergement plus bas et des besoins d'infrastructure réduits.
  • Avantage concurrentiel : Une application rapide et efficace vous distingue de vos concurrents dont les applications peuvent être plus lentes ou moins optimisées. Selon une recherche de Portent, un site web qui se charge en une seconde a un taux de conversion cinq fois plus élevé qu'un site qui met dix secondes à se charger. Par conséquent, garantir que vos applications React performantes est crucial pour retenir les utilisateurs et maintenir un avantage concurrentiel.

8 Techniques d'optimisation des performances React

Voici huit techniques d'optimisation des performances React que vous pouvez utiliser pour accélérer vos applications.

Visualisation de liste

La visualisation de liste, ou windowing, consiste à rendre uniquement les éléments actuellement visibles à l'écran.

Lorsqu'on traite un grand nombre d'éléments dans une liste, le rendu de tous les éléments à la fois peut entraîner des performances lentes et consommer une quantité significative de mémoire. La virtualisation de liste résout ce problème en ne rendant qu'un sous-ensemble des éléments de la liste actuellement visibles dans la vue, ce qui économise des ressources lorsque les utilisateurs font défiler la liste.

La technique de virtualisation remplace dynamiquement les éléments rendus par de nouveaux, maintenant la portion visible de la liste à jour et réactive. Elle permet de rendre efficacement de grandes listes ou des données tabulaires en ne rendant que la portion visible, en recyclant les composants si nécessaire et en optimisant les performances de défilement.

Il existe différentes approches pour implémenter la visualisation de liste dans React, et l'une d'elles consiste à utiliser une bibliothèque populaire appelée React Virtualized.

Pour installer react-virtualized, vous pouvez utiliser la commande suivante :

npm install react-virtualized --save

Après avoir installé react-virtualized, vous pouvez importer les composants et styles requis. Voici un exemple de la façon d'utiliser le composant List pour créer une liste virtualisée :

import React from 'react';
import { List } from 'react-virtualized';
import 'react-virtualized/styles.css'; // Importer les styles

// Vos données de liste
const list = Array(5000).fill().map((_, index) => ({
  id: index,
  name: `Item ${index}`
}));

// Fonction pour rendre chaque ligne
function rowRenderer({ index, key, style }) {
  return (
    <div key={key} style={style}>
      {list[index].name}
    </div>
  );
}

// Composant principal
function MyVirtualizedList() {
  return (
    <List
      width={300}
      height={300}
      rowCount={list.length}
      rowHeight={20}
      rowRenderer={rowRenderer}
    />
  );
}

export default MyVirtualizedList;

Dans cet exemple, List est le composant principal fourni par react-virtualized. La fonction rowRenderer définit comment chaque ligne doit être rendue. Les props width, height, rowCount, rowHeight et rowRenderer sont essentielles pour configurer le comportement et l'apparence de la liste.

Les applications React peuvent gérer de grandes quantités de données en utilisant la virtualisation de liste sans sacrifier les performances ou l'expérience utilisateur.

Chargement paresseux des images

Similaire à la technique de virtualisation de liste, le chargement paresseux des images empêche la création de nœuds DOM inutiles, améliorant ainsi les performances. Le chargement paresseux vous permet de différer ou de retarder le chargement des images jusqu'à ce qu'elles soient nécessaires ou visibles pour l'utilisateur, au lieu de charger toutes les images au chargement de la page.

Le concept derrière le chargement paresseux est d'initier le chargement d'un espace réservé ou d'une version de faible résolution de l'image, généralement une miniature de petite taille ou un espace réservé flou. Lorsque l'utilisateur fait défiler ou interagit avec la page, l'image réelle est chargée dynamiquement, remplaçant l'espace réservé lorsque l'utilisateur entre dans la zone visible ou lorsqu'elle devient visible.

Le chargement paresseux dans React peut être réalisé en utilisant diverses bibliothèques et techniques. L'une des bibliothèques populaires est react-lazyload.

Pour installer react-lazyload, vous pouvez utiliser la commande suivante :

npm install --save react-lazyload

Voici un exemple d'un simple composant React qui utilise react-lazyload pour implémenter le chargement paresseux des images :

import React from 'react';
import LazyLoad from 'react-lazyload';

const MyLazyLoadedImage = ({ src, alt }) => {
  return (
    <LazyLoad height={200} offset={100}>
      {/* Les props height et offset contrôlent quand l'image doit commencer à charger */}
      <img src={src} alt={alt} />
    </LazyLoad>
  );
};

export default MyLazyLoadedImage;

Dans cet exemple, MyLazyLoadedImage utilise le composant LazyLoad de react-lazyload. La prop height spécifie la hauteur de l'espace réservé, et la prop offset détermine à quelle distance en dessous de la zone visible l'espace réservé doit commencer à charger.

Une autre approche consiste à utiliser l'API Intersection Observer, qui est une API web permettant de détecter efficacement quand un élément entre ou sort de la zone visible. Voici comment nous pouvons utiliser l'API Intersection Observer avec le hook useEffect dans React :

import React, { useEffect, useRef } from 'react';

const IntersectionLazyLoad = ({ src, alt }) => {
  const imageRef = useRef();

  useEffect(() => {
    const options = {
      root: null, // Utiliser la zone visible comme racine
      rootMargin: '0px', // Aucune marge autour de la racine
      threshold: 0.5, // 50 % de l'image doit être visible
    };

    const observer = new IntersectionObserver(handleIntersection, options);

    if (imageRef.current) {
      observer.observe(imageRef.current);
    }

    return () => {
      // Nettoyer l'observateur lorsque le composant est démonté
      observer.disconnect();
    };
  }, []);

  const handleIntersection = (entries) => {
    entries.forEach((entry) => {
      if (entry.isIntersecting) {
        // Charger l'image lorsqu'elle devient visible
        imageRef.current.src = src;
        imageRef.current.alt = alt;
      }
    });
  };

  return <img ref={imageRef} style={{ height: '200px' }} alt="Placeholder" />;
};

export default IntersectionLazyLoad;

Dans cet exemple, IntersectionLazyLoad utilise l'API Intersection Observer pour déterminer quand l'image devient visible dans la zone visible.

En utilisant cette API avec le hook useEffect de React, vous pouvez implémenter votre propre solution de chargement paresseux pour les images dans React.

Mémoisation

La mémoisation dans React est une technique utilisée pour optimiser les performances des composants fonctionnels en mettant en cache les résultats de calculs coûteux ou d'appels de fonctions. Elle est particulièrement utile lorsqu'on traite des fonctions intensives en calcul ou fréquemment appelées avec les mêmes valeurs d'entrée, car elle aide à éviter les calculs redondants et améliore l'efficacité globale de l'application.

Dans React, il existe trois techniques de mémoisation : React.memo(), useMemo(), et useCallback(). Examinons les détails de chacune :

Comment utiliser React.memo()

Ce composant d'ordre supérieur enveloppe des composants purement fonctionnels pour empêcher le re-rendu si les props reçues restent inchangées.

En utilisant React.memo(), le résultat du rendu est mis en cache en fonction des props. Si les props n'ont pas changé depuis le dernier rendu, React réutilise le résultat précédemment rendu au lieu de refaire le processus de rendu. Cela économise du temps et des ressources.

Voici un exemple de la façon d'utiliser React.memo avec un composant fonctionnel :

import React from 'react';

const Post = ({ signedIn, post }) => {
  console.log('Rendering Post');
  return (
    <div>
      <h2>{post.title}</h2>
      <p>{post.content}</p>
      {signedIn && <button>Edit Post</button>}
    </div>
  );
};

export default React.memo(Post);

Dans le code ci-dessus, Post (composant fonctionnel) dépend des props signedIn et post. En l'enveloppant avec React.memo(), React ne re-rendra le composant Post que si signedIn ou post change.

Vous pouvez maintenant utiliser le composant mémoisé comme n'importe quel autre composant dans votre application :

import React, { useState } from 'react';
import Post from './Post';

const App = () => {
  const [signedIn, setSignedIn] = useState(false);
  const post = { title: 'Hello World', content: 'Welcome to my blog!' };

  return (
    <div>
      <Post signedIn={signedIn} post={post} />
      <button onClick={() => setSignedIn(!signedIn)}>
        Toggle Signed In
      </button>
    </div>
  );
};

export default App;

Lorsque vous cliquez sur le bouton Toggle Signed In, il bascule l'état signedIn. Comme Post est enveloppé avec React.memo(), il ne se re-rendra que lorsque la prop signedIn change, économisant ainsi du temps et des ressources de rendu.

Comment utiliser useMemo()

Le hook useMemo() optimise les performances en mémoïsant le résultat d'un appel de fonction ou d'un calcul coûteux. Il met en cache le résultat et le recalcule uniquement lorsque les valeurs d'entrée changent. Voici un exemple de la façon d'utiliser le hook useMemo dans un composant fonctionnel :

import React, { useMemo } from 'react';

function App() {
  const [count, setCount] = React.useState(0);
  const [otherState, setOtherState] = React.useState('');

  const expensiveComputation = (num) => {
    let i =  0;
    while (i <  1000000000) i++;
    return num * num;
  };

  const memoizedValue = useMemo(() => expensiveComputation(count), [count]);

  return (
    <div>
      <p>Count: {count}</p>
      <p>Square: {memoizedValue}</p>
      <button onClick={() => setCount(count +  1)}>Increase Count</button>
      <input type="text" onChange={(e) => setOtherState(e.target.value)} />
    </div>
  );
}

export default App;

Dans le code ci-dessus, la fonction expensiveComputation simule une opération intensive en ressources, comme élever un nombre au carré.

Le hook useMemo est utilisé pour mettre en cache le résultat de ce calcul. La valeur mémoïsée, stockée dans memoizedValue, n'est recalculée que lorsque l'état count change, car count est spécifié comme une dépendance dans le tableau de dépendances de useMemo. Par conséquent, cliquer sur le bouton Increase Count incrémente l'état count, déclenchant un recalcul de la valeur mémoïsée.

Inversement, changer otherState via le champ de saisie ne provoque pas de recalcul, car otherState n'est pas inclus dans le tableau de dépendances de useMemo.

Comment utiliser useCallback()

Le hook useCallback() dans React est utilisé pour mémoïser une fonction au lieu de mémoïser le résultat de la fonction. Il est particulièrement utile lors du passage d'événements en tant que props à des composants enfants pour éviter des re-rendus inutiles.

useCallback() mémoïse la fonction, garantissant qu'elle reste la même entre les re-rendus tant que les dépendances n'ont pas changé.

Cela est particulièrement bénéfique lors du passage de fonctions en tant que props à des composants enfants, empêchant des re-rendus inutiles. Il est souvent utilisé avec React.memo() pour s'assurer que les composants enfants ne se re-rendent pas lorsque c'est inutile. Voici un exemple de la façon d'utiliser le hook useCallback() :

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

const ParentComponent = () => {
  const [count, setCount] = useState(0);

  // Définir une fonction qui incrémente l'état count
  const incrementCount = () => {
    setCount(count + 1);
  };

  // Mémoïser la fonction incrementCount en utilisant useCallback
  const memoizedIncrement = useCallback(incrementCount, [count]);

  return (
    <div>
      <p>Count: {count}</p>
      <ChildComponent onIncrement={memoizedIncrement} />
    </div>
  );
};

const ChildComponent = React.memo(({ onIncrement }) => {
  console.log('Child component rendered');
  return (
    <div>
      <button onClick={onIncrement}>Increment Count</button>
    </div>
  );
});

export default ParentComponent;

Dans le code ci-dessus, le ParentComponent est responsable de la gestion d'une variable d'état nommée count et introduit une fonction appelée incrementCount, qui gère l'incrémentation du compte. En utilisant le hook useCallback, la fonction incrementCount est mémoïsée, garantissant sa stabilité entre les rendus sauf si l'une de ses dépendances, dans ce cas count, subit des changements.

D'autre part, le ChildComponent est un composant imbriqué dans le parent. Il reçoit la fonction mémoïsée onIncrement du parent en tant que prop.

Pour optimiser les performances et éviter les re-rendus inutiles lorsque les props restent constantes, le ChildComponent est enveloppé avec React.memo(). Cela garantit que le composant enfant ne se re-rendra que lorsque ses props, spécifiquement la fonction mémoïsée, subissent des changements, contribuant à un processus de rendu plus efficace.

Il est important de noter que useCallback doit être utilisé avec parcimonie et uniquement pour les parties critiques en termes de performance de votre application. Une utilisation excessive de useCallback peut en fait entraîner une performance pire en raison de la surcharge de la mémoïsation elle-même. Mesurez toujours l'impact sur les performances avant et après l'utilisation de useCallback pour vous assurer qu'il a l'effet souhaité.

Throttling et Debouncing des événements

Le throttling dans React est une technique utilisée pour limiter le nombre de fois qu'une fonction ou un gestionnaire d'événements est invoqué. Il garantit que la fonction est appelée à un intervalle spécifié, l'empêchant d'être exécutée trop fréquemment.

Le throttling vous permet de contrôler la fréquence à laquelle la fonction est appelée en définissant un intervalle de temps minimum entre chaque invocation de la fonction. Si la fonction est appelée plusieurs fois dans cet intervalle, seule la première invocation est exécutée, et les invocations suivantes sont ignorées jusqu'à ce que l'intervalle s'écoule.

Maintenant, illustrons le throttling avec un exemple de code. D'abord, sans throttling :

// Sans throttling, cette fonction sera appelée chaque fois que l'événement est déclenché
function handleResize() {
  console.log('Window resized');
}

window.addEventListener('resize', handleResize);

Avec le throttling, nous pouvons limiter la fréquence à laquelle la fonction handleResize est appelée :

// Fonction de throttling
function throttle(func, delay) {
  let lastCall =  0;
  return function(...args) {
    const now = new Date().getTime();
    if (now - lastCall < delay) {
      return;
    }
    lastCall = now;
    func(...args);
  };
}

// Gestionnaire d'événements avec throttling
const throttledHandleResize = throttle(handleResize,  200);

window.addEventListener('resize', throttledHandleResize)

Dans cet exemple, la fonction throttle enveloppe handleResize et garantit qu'elle n'est pas appelée plus souvent que toutes les 200 millisecondes. Si l'événement resize se déclenche plus fréquemment que cela, la fonction handleResize ne sera exécutée qu'une fois toutes les 200 millisecondes, réduisant ainsi le potentiel de problèmes de performance causés par des appels de fonction rapides et répétés.

Le debouncing, en revanche, est également utilisé pour limiter le nombre de fois qu'une fonction ou un gestionnaire d'événements est invoqué. Il garantit que la fonction est appelée uniquement après une certaine période d'inactivité. Le debouncing vous permet de reporter l'appel de la fonction jusqu'à ce que l'utilisateur ait terminé de taper ou qu'un temps spécifique se soit écoulé depuis le dernier événement.

Par exemple, imaginez que vous avez un champ de saisie de recherche et que vous souhaitez déclencher une requête d'API de recherche uniquement lorsque l'utilisateur a terminé de taper pendant une certaine durée, comme 300ms.

Avec le debouncing, la fonction de recherche ne sera invoquée qu'après que l'utilisateur a arrêté de taper pendant 300ms. Si l'utilisateur continue de taper dans cet intervalle, l'appel de la fonction sera retardé jusqu'à ce que la pause se produise. Sans debouncing, la fonction sera appelée à chaque frappe, ce qui pourrait entraîner des appels de fonction excessifs et des calculs inutiles. Démontrons avec un exemple de code :

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

const SearchComponent = () => {
  const [searchTerm, setSearchTerm] = useState('');

  // Fonction pour simuler une requête d'API de recherche
  const searchAPI = (query) => {
    console.log(`Searching for: ${query}`);
    // Dans une application réelle, vous feriez une requête d'API ici
  };

  // Fonction de debouncing pour retarder l'appel de searchAPI
  const debounce = (func, delay) => {
    let timeoutId;
    return function (...args) {
      clearTimeout(timeoutId);
      timeoutId = setTimeout(() => {
        func(...args);
      }, delay);
    };
  };

  // Fonction de recherche avec debouncing
  const debouncedSearch = debounce(searchAPI, 300);

  // useEffect pour surveiller les changements dans searchTerm et déclencher debouncedSearch
  useEffect(() => {
    debouncedSearch(searchTerm);
  }, [searchTerm, debouncedSearch]);

  // Gestionnaire d'événements pour la saisie de recherche
  const handleSearchChange = (event) => {
    setSearchTerm(event.target.value);
  };

  return (
    <div>
      <label htmlFor="search">Search:</label>
      <input
        type="text"
        id="search"
        value={searchTerm}
        onChange={handleSearchChange}
        placeholder="Type to search..."
      />
    </div>
  );
};

export default SearchComponent;

Avec cette configuration, la fonction searchAPI ne sera invoquée qu'après que l'utilisateur a arrêté de taper pendant 300ms, empêchant ainsi les requêtes d'API excessives et améliorant les performances globales de la fonctionnalité de recherche.

Division de code

La division de code dans React est une technique utilisée pour diviser un grand bundle JavaScript en morceaux plus petits et gérables. Elle aide à améliorer les performances en chargeant uniquement le code nécessaire pour une partie spécifique d'une application plutôt que de charger l'ensemble du bundle d'emblée.

Lorsque vous développez une nouvelle application React, tout votre code JavaScript est généralement regroupé en un seul fichier. Ce fichier contient tous les composants, bibliothèques et autre code requis pour que votre application fonctionne. Mais à mesure que votre application grandit, la taille du bundle peut devenir assez grande, entraînant des temps de chargement initiaux lents pour vos utilisateurs.

La division de code vous permet de diviser un seul bundle en plusieurs morceaux, qui peuvent être chargés de manière sélective en fonction des besoins actuels de votre application. Au lieu de télécharger l'ensemble du bundle d'emblée, seul le code nécessaire est récupéré et exécuté lorsque l'utilisateur visite une page particulière ou déclenche une action spécifique.

Voici un exemple de base de division de code :

// AsyncComponent.js
import React, { lazy, Suspense } from 'react';

const DynamicComponent = lazy(() => import('./DynamicComponent'));

const AsyncComponent = () => (
  <Suspense fallback={<div>Loading...</div>}>
    <DynamicComponent />
  </Suspense>
);

export default AsyncComponent;


// DynamicComponent.js
import React from 'react';

const DynamicComponent = () => (
  <div>
    <p>This is a dynamically loaded component!</p>
  </div>
);

export default DynamicComponent;

Dans cet exemple, AsyncComponent est un composant qui utilise lazy et Suspense pour effectuer la division de code. Le DynamicComponent est importé dynamiquement en utilisant la syntaxe import().

Lorsque AsyncComponent est rendu, React chargera DynamicComponent uniquement lorsqu'il est nécessaire, réduisant la taille initiale du bundle et améliorant les performances de l'application. La prop fallback dans Suspense spécifie ce qu'il faut rendre en attendant que l'import dynamique soit résolu, offrant une meilleure expérience utilisateur pendant le processus de chargement.

Fragments React

Les Fragments React sont une fonctionnalité introduite dans React 16.2 qui permet de regrouper plusieurs éléments ensemble sans ajouter un nœud DOM supplémentaire. Cela est particulièrement utile lorsque vous devez retourner plusieurs éléments à partir de la méthode de rendu d'un composant, mais que vous ne souhaitez pas introduire des éléments DOM inutiles qui pourraient affecter la mise en page ou les styles de votre application.

Imaginez que vous arrangez des livres sur une étagère. Chaque livre représente un composant React, et l'étagère représente le DOM.

Normalement, si vous avez plusieurs livres, vous pourriez vouloir les regrouper sous une étiquette de catégorie (analogue à un élément DOM comme un <div>). Mais parfois, vous voulez simplement placer les livres côte à côte sans étiquette car l'étiquette elle-même n'a aucune valeur et ne prend que de la place physique.

Les Fragments React sont comme l'option d'arranger les livres sans étiquette, économisant de l'espace et rendant l'arrangement plus propre.

Voici un exemple de la façon d'utiliser les fragments React :

import React from 'react';

function BookShelf() {
  return (
    <>
      <Book title="React for Beginners" />
      <Book title="Mastering Redux" />
      <Book title="JavaScript Essentials" />
    </>
  );
}

function Book({ title }) {
  return <li>{title}</li>;
}

export default BookShelf;

Dans cet exemple, le composant BookShelf retourne une liste de composants Book sans les envelopper dans un <div> ou un autre élément DOM inutile. Au lieu de cela, il utilise la syntaxe abrégée <> pour les Fragments React.

Cela donne une structure DOM plus propre, ce qui peut améliorer les performances de votre application React en réduisant le nombre d'éléments que le navigateur doit traiter et rendre. L'utilisation de fragments peut également réduire le balisage inutile et contribuer à un arbre de rendu plus propre et plus efficace.

Web Workers

JavaScript fonctionne comme une application monothread conçue pour gérer des tâches synchrones.

Lorsqu'une page web est en cours de rendu, JavaScript exécute plusieurs tâches, y compris la manipulation d'éléments DOM, la gestion des interactions UI, la gestion des données de réponse d'API et l'activation des animations CSS, le tout dans un seul thread. Malgré son efficacité dans la gestion de ces tâches, leur exécution dans un seul thread peut parfois entraîner des goulots d'étranglement de performance.

Les Web Workers servent de solution pour atténuer la charge sur le thread principal. Ils permettent l'exécution de scripts en arrière-plan sur un thread séparé, distinct du thread principal JavaScript.

Cette séparation permet la gestion de tâches intensives en calcul, l'exécution d'opérations de longue durée ou la gestion de tâches qui pourraient autrement bloquer le thread principal. En faisant cela, les Web Workers contribuent à maintenir la réactivité de l'interface utilisateur et les performances globales de l'application.

Pour utiliser un web worker dans React, créez un nouveau fichier JavaScript qui contiendra le code pour le thread worker :

// worker.js
self.onmessage = function(event) {
  var input = event.data;
  var result = performHeavyComputation(input);
  postMessage(result);
};

function performHeavyComputation(input) {
  // Insérez votre logique de calcul intensif ici
  return input *   2; // Juste une opération de remplacement
}

Dans votre composant React, instanciez le Web Worker et établissez un canal de communication avec lui :

import React, { useEffect, useRef } from 'react';

function MyComponent() {
  const workerRef = useRef();

  useEffect(() => {
    // Initialiser le worker
    workerRef.current = new Worker('path-to-your-worker-file.js');

    // Gérer les messages entrants du worker
    workerRef.current.onmessage = (event) => {
      console.log('Message reçu du worker:', event.data);
    };

    // Nettoyer le worker lorsque le composant est démonté
    return () => {
      workerRef.current.terminate();
    };
  }, []);

  // Fonction pour envoyer un message au worker
  const sendMessageToWorker = (message) => {
    workerRef.current.postMessage(message);
  };

  // Reste de votre composant
  return (
    // ...
  );
}

Dans cet exemple, un Web Worker est initialisé dans le hook useEffect et stocké dans une ref pour une utilisation future. Les messages du worker sont gérés avec un écouteur d'événements onmessage, et le worker est terminé lorsque le composant est démonté pour nettoyer les ressources. La fonction sendMessageToWorker démontre comment communiquer avec le worker en utilisant postMessage.

Hook UseTransition

Le hook useTransition dans React joue un rôle pivot dans l'amélioration des performances des applications en permettant de marquer les mises à jour d'état comme des transitions non bloquantes. Cette capacité permet à React de différer le rendu pour ces mises à jour, empêchant le blocage de l'UI et améliorant la réactivité globale.

Lors de l'utilisation de useTransition, les mises à jour d'état dans la fonction startTransition sont traitées comme des transitions de faible priorité, susceptibles d'être interrompues par des mises à jour d'état de priorité plus élevée. Ainsi, si une mise à jour de haute priorité se produit pendant une transition, React peut prioriser la fin de la mise à jour de haute priorité, interrompant la transition en cours.

Ce mécanisme de transition non bloquant est précieux pour prévenir le blocage de l'UI pendant des opérations intensives telles que la récupération de données ou des mises à jour à grande échelle. En différant le rendu des composants associés aux mises à jour de transition, React garantit que l'interface utilisateur reste réactive même dans des scénarios où l'UI pourrait autrement devenir non réactive.

Cet exemple démontre l'utilisation de useTransition dans un composant React :

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

function MyComponent() {
  const [state, setState] = useState(initialState);
  const [isPending, startTransition] = useTransition();

  function handleClick() {
    startTransition(() => {
      setState(newState); // Cette mise à jour d'état est marquée comme une transition
    });
  }

  return (
    <>
      {/* Votre JSX de composant */}
      <button onClick={handleClick}>Update State</button>
      {isPending && <div>Loading...</div>}
    </>
  );
}

Cet exemple montre comment React évite de bloquer l'UI pendant les transitions déclenchées par les actions de l'utilisateur, permettant une interruption si des mises à jour d'état de priorité plus élevée sont détectées.

Notez que useTransition fait partie de l'API Concurrent Mode, introduite dans React 18 et les versions ultérieures. En tant qu'outil puissant pour modifier le comportement par défaut des mises à jour d'état, assurez-vous de l'utiliser avec soin, en tenant compte des implications spécifiques du report du rendu dans le contexte de votre application.

Conclusion

L'optimisation des performances d'une application React implique une combinaison de stratégies, allant de la compréhension fondamentale de l'algorithme de diffing de React à l'utilisation de fonctionnalités intégrées et d'outils tiers.

En appliquant ces techniques de manière judicieuse, vous pouvez créer des applications qui sont non seulement visuellement attrayantes mais aussi performantes, conduisant à une meilleure expérience utilisateur globale.