Libérez la puissance de useRef dans React. Explorez divers cas d'usage, incluant l'accès direct au DOM, le maintien de valeurs mutables et l'optimisation du comportement des composants fonctionnels.
React useRef : Maîtriser les Patrons de Stockage de Valeurs Mutables
useRef est un hook puissant de React qui offre un moyen de conserver des valeurs entre les rendus sans provoquer de nouveaux rendus lorsque ces valeurs changent. Il est souvent associé à l'accès direct aux éléments du DOM, mais ses capacités vont bien au-delà. Ce guide complet explorera les divers cas d'utilisation de useRef, vous permettant d'écrire du code React plus efficace et plus maintenable.
Comprendre useRef : Bien plus que l'accès au DOM
Fondamentalement, useRef renvoie un objet ref mutable dont la propriété .current est initialisée avec l'argument passé (initialValue). L'objet retourné persistera pendant toute la durée de vie du composant. Fait crucial, la modification de la propriété .current ne déclenche pas de nouveau rendu. C'est la différence clé entre useRef et useState.
Bien que l'accès aux éléments du DOM soit un cas d'usage courant, useRef excelle dans la gestion de toute valeur mutable qui n'a pas besoin de provoquer un nouveau rendu lorsqu'elle est mise à jour. Cela le rend inestimable pour des tâches telles que :
- Stocker les valeurs précédentes des props ou de l'état.
- Maintenir des compteurs ou des minuteurs.
- Suivre l'état de focus sans provoquer de nouveaux rendus.
- Stocker toute valeur mutable qui doit persister entre les rendus.
Utilisation de base : Accéder aux éléments du DOM
Le cas d'usage le plus connu est l'accès direct aux éléments du DOM. C'est utile pour les scénarios où vous devez interagir impérativement avec un nœud du DOM, comme donner le focus à un champ de saisie, mesurer ses dimensions ou déclencher des animations.
Exemple : Donner le focus à un champ de saisie
Voici comment vous pouvez utiliser useRef pour donner le focus à un champ de saisie lorsqu'un composant est monté :
import React, { useRef, useEffect } from 'react';
function MyComponent() {
const inputRef = useRef(null);
useEffect(() => {
// Focus the input field on mount
if (inputRef.current) {
inputRef.current.focus();
}
}, []); // Empty dependency array ensures this runs only once on mount
return (
<input type="text" ref={inputRef} placeholder="Enter text" />
);
}
export default MyComponent;
Explication :
- Nous créons une ref en utilisant
useRef(null). La valeur initiale estnullcar l'élément input n'existe pas encore lors du rendu initial du composant. - Nous attachons la ref à l'élément input en utilisant la prop
ref:ref={inputRef}. React assignera automatiquement le nœud du DOM àinputRef.currentlorsque l'élément input sera monté. - Nous utilisons
useEffectavec un tableau de dépendances vide ([]) pour nous assurer que l'effet ne s'exécute qu'une seule fois après le montage du composant. - À l'intérieur de l'effet, nous vérifions si
inputRef.currentexiste (pour éviter les erreurs si l'élément n'est pas encore disponible) puis nous appelonsinputRef.current.focus()pour donner le focus au champ de saisie.
Au-delà de l'accès au DOM : Gérer les valeurs mutables
La véritable puissance de useRef réside dans sa capacité à stocker des valeurs mutables qui persistent entre les rendus sans déclencher de nouveaux rendus. Cela ouvre un large éventail de possibilités pour optimiser le comportement des composants et gérer l'état dans les composants fonctionnels.
Exemple : Stocker les valeurs précédentes des props ou de l'état
Parfois, vous avez besoin d'accéder à la valeur précédente d'une prop ou d'une variable d'état. useRef offre un moyen propre de le faire sans déclencher de nouveaux rendus inutiles.
import React, { useRef, useEffect } from 'react';
function MyComponent({ value }) {
const previousValue = useRef(value);
useEffect(() => {
// Update the ref's .current property with the current value
previousValue.current = value;
}, [value]); // Effect runs whenever the 'value' prop changes
// Now you can access the previous value using previousValue.current
return (
<div>
Current value: {value}
<br />
Previous value: {previousValue.current}
</div>
);
}
export default MyComponent;
Explication :
- Nous initialisons la ref
previousValueavec la valeur initiale de la propvalue. - Nous utilisons
useEffectpour mettre à jour la propriétépreviousValue.currentchaque fois que la propvaluechange. - À l'intérieur du composant, nous pouvons maintenant accéder à la valeur précédente de la prop
valueen utilisantpreviousValue.current.
Exemple de cas d'usage : Suivre les changements dans les réponses d'API (Scénario international)
Imaginez que vous construisez un tableau de bord qui affiche les taux de change de devises récupérés depuis une API. L'API pourrait renvoyer les taux dans différents formats ou avec des niveaux de précision variables selon la source de données (par exemple, une API de la Banque Centrale Européenne par rapport à une API d'une institution financière d'Asie du Sud-Est). Vous pouvez utiliser useRef pour suivre le taux de change précédent et afficher un indicateur visuel (par exemple, une flèche verte vers le haut ou une flèche rouge vers le bas) pour montrer si le taux a augmenté ou diminué depuis la dernière mise à jour. C'est crucial pour les utilisateurs internationaux qui se fient à ces taux pour leurs décisions financières.
Exemple : Maintenir des compteurs ou des minuteurs
useRef est parfait pour gérer des compteurs ou des minuteurs qui n'ont pas besoin de déclencher de nouveaux rendus. Par exemple, vous pourriez l'utiliser pour suivre le nombre de fois qu'un bouton a été cliqué ou pour implémenter un simple minuteur.
import React, { useRef, useState, useEffect } from 'react';
function MyComponent() {
const [count, setCount] = useState(0);
const clickCount = useRef(0); // Initialize the ref with 0
const handleClick = () => {
clickCount.current++; // Increment the ref's .current property
setCount(clickCount.current); //Increment state which re-renders.
};
return (
<div>
<p>Button clicked: {count} times</p>
<button onClick={handleClick}>Click me</button>
</div>
);
}
export default MyComponent;
Explication :
- Nous initialisons une ref
clickCountavec la valeur 0. - Dans la fonction
handleClick, nous incrémentons la propriétéclickCount.current. Cela ne déclenche pas de nouveau rendu. - Nous mettons également à jour l'état 'count', ce qui déclenche un nouveau rendu.
Exemple : Implémenter une fonction de "debounce"
Le "debouncing" est une technique utilisée pour limiter la fréquence à laquelle une fonction est exécutée. Elle est couramment utilisée dans les champs de saisie de recherche pour éviter les appels d'API excessifs pendant que l'utilisateur tape. useRef peut être utilisé pour stocker l'ID du minuteur utilisé dans la fonction de debounce.
import React, { useState, useRef, useEffect } from 'react';
function MyComponent() {
const [searchTerm, setSearchTerm] = useState('');
const [results, setResults] = useState([]);
const timerRef = useRef(null); // Store the timer ID
const handleChange = (event) => {
const newSearchTerm = event.target.value;
setSearchTerm(newSearchTerm);
// Clear the previous timer if it exists
if (timerRef.current) {
clearTimeout(timerRef.current);
}
// Set a new timer
timerRef.current = setTimeout(() => {
// Simulate an API call
fetch(`https://api.example.com/search?q=${newSearchTerm}`)
.then(response => response.json())
.then(data => setResults(data.results));
}, 300); // Debounce for 300 milliseconds
};
return (
<div>
<input
type="text"
placeholder="Search..."
value={searchTerm}
onChange={handleChange}
/>
<ul>
{results.map(result => (
<li key={result.id}>{result.name}</li>
))}
</ul>
</div>
);
}
export default MyComponent;
Explication :
- Nous utilisons
useRefpour stocker l'ID du minuteur danstimerRef. - Dans la fonction
handleChange, nous effaçons le minuteur précédent (s'il existe) en utilisantclearTimeout(timerRef.current). - Nous définissons ensuite un nouveau minuteur en utilisant
setTimeoutet stockons l'ID du minuteur danstimerRef.current. - L'appel d'API n'est effectué qu'après que l'utilisateur a cessé de taper pendant 300 millisecondes.
Considérations sur l'internationalisation : Lors de l'implémentation du "debouncing" avec des appels d'API qui affichent des informations dans différentes langues, assurez-vous que votre API prend en charge l'internationalisation et renvoie les données dans la langue préférée de l'utilisateur. Envisagez d'utiliser l'en-tête Accept-Language dans vos requêtes API.
Exemple : Suivre l'état de focus sans nouveaux rendus
Vous pouvez utiliser useRef pour savoir si un élément a le focus sans provoquer de nouveaux rendus. Cela peut être utile pour styliser les éléments en fonction de leur état de focus ou pour implémenter une logique de gestion de focus personnalisée.
import React, { useRef, useState } from 'react';
function MyComponent() {
const [isFocused, setIsFocused] = useState(false);
const inputRef = useRef(null);
const handleFocus = () => {
setIsFocused(true);
};
const handleBlur = () => {
setIsFocused(false);
};
return (
<div>
<input
type="text"
ref={inputRef}
onFocus={handleFocus}
onBlur={handleBlur}
/>
<p>Input is focused: {isFocused ? 'Yes' : 'No'}</p>
</div>
);
}
export default MyComponent;
useRef vs. useState : Choisir le bon outil
Il est important de comprendre les différences clés entre useRef et useState pour choisir le bon outil pour la tâche.
| Caractéristique | useRef | useState |
|---|---|---|
| Déclenche un nouveau rendu | Non | Oui |
| Objectif | Stocker des valeurs mutables qui n'ont pas besoin de déclencher de nouveaux rendus. Accéder aux éléments du DOM. | Gérer l'état qui doit déclencher des nouveaux rendus. |
| Persistance | Persiste entre les rendus. | Persiste entre les rendus, mais la valeur est mise à jour via la fonction de mise à jour (setter). |
Meilleures pratiques et pièges courants
- Ne mutez pas l'état directement : Bien que
useRefvous permette de muter des valeurs directement, évitez de muter directement les variables d'état gérées paruseState. Utilisez toujours la fonction de mise à jour fournie paruseStatepour modifier l'état. - Soyez attentif aux effets de bord : Lorsque vous utilisez
useRefpour gérer des valeurs qui affectent l'interface utilisateur, soyez conscient des effets de bord potentiels. Assurez-vous que votre code se comporte de manière prévisible et n'introduit pas de bugs inattendus. - Ne vous fiez pas à
useRefpour la logique de rendu : Étant donné que les changements deuseRefne déclenchent pas de nouveaux rendus, ne vous fiez pas directement à ses valeurs pour déterminer ce qui doit être rendu. UtilisezuseStatepour les valeurs qui doivent piloter la logique de rendu. - Considérez les implications sur les performances : Bien que
useRefpuisse aider à optimiser les performances en évitant des rendus inutiles, sachez qu'une utilisation excessive de valeurs mutables peut rendre votre code plus difficile à comprendre et à déboguer.
Cas d'usage et patrons avancés
Persistance des valeurs à travers les instances de composants
Alors que `useRef` persiste les valeurs à travers les rendus d'une *unique* instance de composant, vous avez parfois besoin qu'une valeur persiste à travers *différentes* instances du même composant. Cela nécessite une approche légèrement différente, s'appuyant souvent sur une variable au niveau du module combinée à `useRef`.
// myComponent.js
let globalCounter = 0; // Module-level variable
import React, { useRef, useEffect } from 'react';
function MyComponent() {
const counterRef = useRef(globalCounter); // Initialize with the global value
useEffect(() => {
// Update the global counter whenever the ref changes
globalCounter = counterRef.current;
}, [counterRef.current]);
const increment = () => {
counterRef.current++;
//No setState needed, so no re-render
};
return (
<div>
<p>Counter: {counterRef.current}</p>
<button onClick={increment}>Increment</button>
</div>
);
}
export default MyComponent;
Considérations importantes : Ce patron introduit une variable globale, soyez donc extrêmement prudent quant aux effets de bord potentiels et aux conditions de concurrence ("race conditions"), en particulier dans les applications complexes. Envisagez des approches alternatives comme l'utilisation d'un fournisseur de contexte (context provider) si la valeur doit être partagée entre plusieurs composants de manière plus contrôlée.
Conclusion : Libérer la puissance de useRef
useRef est un outil polyvalent dans React qui va bien au-delà du simple accès aux éléments du DOM. En comprenant sa capacité à stocker des valeurs mutables sans déclencher de nouveaux rendus, vous pouvez optimiser vos composants, gérer l'état plus efficacement et construire des applications React plus performantes et maintenables. N'oubliez pas de l'utiliser judicieusement et de toujours considérer les compromis potentiels entre performance et clarté du code.
En maîtrisant les patrons décrits dans ce guide, vous serez bien équipé pour exploiter tout le potentiel de useRef dans vos projets React, que vous construisiez une simple application web ou un système d'entreprise complexe. N'oubliez pas de prendre en compte l'internationalisation et l'accessibilité lorsque vous développez pour un public mondial !