J'essaie d'en apprendre davantage sur la fonctionnalité des crochets, mais je n'arrive pas à comprendre comment je suis censé utiliser correctement la fonction useCallback
. Pour autant que je comprends des règles sur les hooks, je suis censé les appeler de haut niveau et non dans la logique (comme les boucles). Ce qui m'amène à être assez confus sur la façon dont je suis censé implémenter useCallback
sur des composants qui sont rendus à partir d'une boucle.
Jetez un œil à l'extrait d'exemple suivant où je crée 5 boutons avec un gestionnaire onClick
qui imprime le numéro du bouton sur la console.
const Example = (props) => {
const onClick = (key) => {
console.log("You clicked: ", key);
};
return(
<div>
{
_.times(5, (key) => {
return (
<button onClick={() => onClick(key)}>
{key}
</button>
);
})
}
</div>
);
};
console.log("hello there");
ReactDOM.render(<Example/>, document.getElementById('root'));
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id='root'>
</div>
En ce moment, il crée une nouvelle instance de fonction à chaque fois que l'exemple est rendu en raison de mon utilisation de la fonction flèche dans la fonction <button onClick={() => onClick(key)}>
, ce que je fais parce que je veux que la fonction onClick
sache quel bouton l'a appelée . Je pensais que je pouvais empêcher cela avec la nouvelle fonctionnalité React hooks en utilisant useCallback
, mais si je l'appliquais au const onClick
, Il créerait quand même une nouvelle instance à chaque rendu à cause de la fonction de flèche en ligne nécessaire pour donner le paramètre key
, et je ne suis pas autorisé à l'appliquer au rendu dans une boucle pour autant que je sache (surtout si l'ordre de la boucle peut changer à droite?).
Alors, comment pourrais-je implémenter useCallback
dans ce scénario tout en conservant les mêmes fonctionnalités? Est-ce que c'est possible?
La réponse simple ici est que vous ne devriez probablement pas utiliser useCallback
ici. Le point de useCallback
est de passer la même instance de fonction à des composants optimisés (par exemple PureComponent
ou React.memo
Ized composants) pour éviter des redondances inutiles.
Vous n'avez pas affaire à des composants optimisés dans ce cas (ou la plupart des cas, je suppose), il n'y a donc pas vraiment de raison de mémoriser les rappels comme avec useCallback
.
Supposons que la mémorisation soit importante, cependant, la meilleure solution ici est probablement d'utiliser une seule fonction au lieu de cinq: au lieu d'une fonction unique pour chaque bouton porte le key
par fermeture, vous pouvez attacher le key
à l'élément:
<button data-key={key}>{key}</button>
Et puis lisez la clé du event.target.dataset["key"]
Dans un gestionnaire de clic unique:
const Example = (props) => {
// Single callback, shared by all buttons
const onClick = React.useCallback((e) => {
// Check which button was clicked
const key = e.target.dataset["key"]
console.log("You clicked: ", key);
}, [/* dependencies */]);
return(
<div>
{
_.times(5, (key) => {
return (
<button data-key={key} onClick={onClick}>
{key}
</button>
);
})
}
</div>
);
};
console.log("hello there");
ReactDOM.render(<Example/>, document.getElementById('root'));
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id='root'>
</div>
Cela étant dit, il est possible de mémoriser plusieurs fonctions en un seul crochet. useCallback(fn, deps)
est équivalent à useMemo(() => fn, deps)
, et useMemo
peut être utilisé pour mémoriser plusieurs fonctions à la fois:
const clickHandlers = useMemo(() => _.times(5, key =>
() => console.log("You clicked", key)
), [/* dependencies */]);
const Example = (props) => {
const clickHandlers = React.useMemo(() => _.times(5, key =>
() => console.log("You clicked", key)
), [/* dependencies */])
return(
<div>
{
_.times(5, (key) => {
return (
<button onClick={clickHandlers[key]}>
{key}
</button>
);
})
}
</div>
);
};
console.log("hello there");
ReactDOM.render(<Example/>, document.getElementById('root'));
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id='root'>
</div>
Peut-être y a-t-il un cas où cela est utile, mais dans ce cas, je le laisserais seul (et ne me soucierais pas de l'optimisation) ou bien j'utiliserais un seul gestionnaire pour chaque bouton.