web-dev-qa-db-fra.com

React hooks useCallback avec paramètres dans la boucle

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?

16
ApplePearPerson

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.

15
Retsam