web-dev-qa-db-fra.com

Accessoires par défaut dans les composants fonctionnels avec état (utilisant des crochets)

J'ai parcouru plusieurs questions sur SO concernant les accessoires par défaut pour les composants fonctionnels et ils recommandent tous d'utiliser les paramètres par défaut ES6. Voici des liens vers ces questions.


Cependant, lorsque j'utilise cette méthode pour écrire des composants avec des effets s'exécutant sur le changement d'accessoires, j'obtiens un comportement indésirable avec les non-primitives. Par exemple, le code suivant entraînera une boucle infinie.

const Parent = () => {
  let somethingUndefined;

  return (
    <div>
      <Child prop={somethingUndefined} />
    </div>
  );
};

const Child = ({ prop = {a: 1} }) => {
  const [x, setX] = React.useState(1);

  React.useEffect(() => {
    setX(x + 1);
  }, [prop]);

  return <div>{x}, {prop.a}</div>;
};

ReactDOM.render(<Parent />, document.getElementsByTagName('body')[0]);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.11.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script>

J'ai essayé deux façons d'essayer de contourner le problème. Tout d'abord, en affectant simplement une variable différente qui contient la valeur par défaut, et en plaçant le prop non modifié dans le tableau de dépendances. c'est à dire

const Child = ({ prop }) => {
  const [x, setX] = React.useState(1);

  const defaultedProp = prop || {a: 1};

  React.useEffect(() => {
    setX(x + 1);
  }, [prop]);
  // Note we use prop and not defaultedProp here to avoid runnning into the issue above.

  return <div>{x}, {defaultedProp.a}</div>;
};

Une autre méthode consisterait simplement à utiliser quelque chose comme (prop || {a:1}) à la place de prop partout où vous l'utilisez, sauf dans le tableau de dépendances. c'est à dire

const Child = ({ prop }) => {
  const [x, setX] = React.useState(1);

  React.useEffect(() => {
    setX(x + 1);
  }, [prop]);

  return <div>{x}, {(prop || {a: 1}).a}</div>;
};

Mais ces deux solutions semblent sous-optimales car cela demanderait beaucoup d'efforts inutiles (et un code volumineux).

defaultProps est également une solution au problème de la boucle infinie mais c'est obsolète . Notez que l'exemple fourni dans ce rfc utilise également les paramètres par défaut ES6 dans le code.

Est-ce que je manque quelque chose? Existe-t-il un meilleur moyen d'utiliser les accessoires par défaut dans les composants fonctionnels avec état qui exécutent des effets sur le changement d'accessoires?

6
ManavM

Je ne sais pas si cela est éligible pour une réponse, mais toutes vos préoccupations pourraient être résolues en déclarant votre valeur par défaut en tant que constante dans l'application. Cela signifie;

const Parent = () => {
  const somethingUndefined;

  return (
    <>
      <Child prop={somethingUndefined} />
    </>
  );
};

const Child = ({ prop = {a: 1} }) => {
  const [x, setX] = React.useState(1);

  React.useEffect(() => {
    setX(x + 1);
  }, [prop]);

  return <div>{x}, {prop.a}</div>;
};

Vous pouvez changer le code ci-dessus en

const Parent = () => {
  const somethingUndefined;

  return (
    <>
      <Child prop={somethingUndefined} />
    </>
  );
};

const defaultPropValue = {a: 1};

const Child = ({ prop = defaultPropValue }) => {
  const [x, setX] = React.useState(1);

  React.useEffect(() => {
    setX(x + 1);
  }, [prop]);

  return <div>{x}, {prop.a}</div>;
};

Cela ne provoquera pas de boucles infinies.

La différence parie ces deux: - Dans le premier, le prop est initialisé à une nouvelle valeur c'est-à-dire, {a: 1} et à chaque mise à jour d'état, ce sera un nouvel objet (le nouvel objet sera dans un nouvel emplacement mémoire), et il invoquera à nouveau le rappel.

Dans le second, nous avons initialisé et assigné le {a: 1} à defaultPropValue qui ne changera pas. Ensuite, nous avons assigné ce defaultPropValue à prop afin qu'à chaque re-rendu, la valeur assignée au prop soit la même (ou à partir du même emplacement mémoire). Cela fonctionne donc comme prévu.

J'espère que l'idée est claire!

6
Ansal Ali

La useEffect() s'exécutera la première fois et invoquera la setX() puis:

  • setX() mettra à jour l'état de x, ce qui déclenchera le nouveau rendu du composant.
  • prop recevra un nouvel objet const Child = ({ prop = {a: 1} }) => {
  • useEffect() s'exécutera à nouveau et invoquera la setX()

tout le processus se répète à nouveau, cela provoque une boucle infinie.

Au lieu de cela, vous pouvez passer une valeur par défaut à la propriété a et l'utiliser dans le tableau de dépendances useEffect()

const Parent = () => {
  let somethingUndefined; // babel complains if we use `const` without value

  return (
    <div>
      <Child prop={somethingUndefined} />      
      <Child prop={{ a: 3 }} />
    </div>
  );
};

const Child = ({ prop = {} }) => {
  const { a = 1 } = prop;
  const [x, setX] = React.useState(1);

  React.useEffect(() => {
    setX(x + 1);
  }, [a]);

  return <div>{x}, {a}</div>;
};

ReactDOM.render(<Parent />, document.getElementsByTagName('body')[0]);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.11.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script>
0
Fraction