web-dev-qa-db-fra.com

React - defaultProps vs paramètres par défaut ES6 lors de la déstructuration (problèmes de performances)

Je viens de rencontrer une question sur les performances de React lorsque les paramètres par défaut sont définis dans l'un de mes composants fonctionnels sans état.

Ce composant avait un defaultProps qui définissait row: false, mais je ne l'aimais pas car le defaultProps est à la fin du fichier, ce qui le rend en fait plus difficile à voir. Et donc, nous ne connaissons pas la propriété par défaut. Je l'ai donc déplacé directement dans la déclaration de fonction et lui ai attribué la valeur par défaut ES6 pour les paramètres.

const FormField = ({
  row = false,
  ...others,
}) => {
  // logic...
};

Mais ensuite nous avons discuté avec un collègue à ce sujet être une bonne idée ou non. Parce que cela peut sembler trivial, mais peut également avoir un grand impact sur les performances puisque react n'est pas au courant de la valeur par défaut.

Je crois que dans ce cas, c'est trivial. Parce que c'est un booléen et non un objet/tableau et ne sera donc pas considéré comme une valeur différente pendant la réconciliation .


Mais voyons maintenant un cas d'utilisation plus avancé:

const FormField = ({
  input: { name, value, ...inputRest },
  label = capitalize(name),
  placeholder = label,
  row = false,
  meta: { touched, error, warning },
  ...others,
}) => {
  // logic...
};

Ici, je base la valeur de placeholder à partir de label, elle-même basée sur input.name. L'utilisation de la déstructuration ES6 avec des valeurs par défaut pour les paramètres rend le tout assez facile à écrire/comprendre et cela fonctionne comme un charme.

Mais est-ce une bonne idée? Et sinon, comment le feriez-vous correctement?

38
Vadorequest

J'ai parlé à plusieurs personnes sur la chaîne Discord #reactiflux et j'ai obtenu la réponse que je cherchais.

Il existe essentiellement trois cas d'utilisation avec les composants React, et dans certains d'entre eux, la destruction des paramètres aura un impact sur les performances, il est donc important de comprendre ce qui se passe sous le capot.

Composant fonctionnel sans état

const MyComponent = ({ name = 'John Doe', displayName = humanize(name), address = helper.getDefaultAddress() }) => {
  return (
    <div>{displayName}</div>
  );
};

Il s'agit d'un composant fonctionnel sans état. Il n'y a pas d'état et il est fonctionnel car il ne s'agit pas d'une instance Class, mais d'une simple fonction.

Dans ce cas, il n'y a pas de cycle de vie, vous ne pouvez pas y avoir componentWillMount ou shouldComponentUpdate ou constructor. Et comme il n'y a pas de gestion du cycle de vie, il n'y a aucun impact sur les performances. Ce code est parfaitement valide. Certains préfèreront peut-être gérer la valeur par défaut displayName dans le corps de la fonction, mais au final cela n'a pas vraiment d'importance, cela n'aura pas d'impact sur les performances.

Composant non fonctionnel sans état

(Ne faites pas ça!)

class MyComponent extends React.Component {
    render() {
        const { name = 'John Doe', displayName = humanize(name), address = helper.getDefaultAddress() } = this.props;
        return (
            <div>{displayName}</div>
          );
    }
}

Il s'agit d'un composant non fonctionnel sans état. Il n'y a pas d'état, mais il n'est pas "fonctionnel" car c'est un class. Et parce que c'est une classe, étendant React.Component, cela signifie que vous aurez un cycle de vie. Vous pouvez y avoir componentWillMount ou shouldComponentUpdate ou constructor.

Et, comme il a un cycle de vie, la façon d'écrire ce composant est mauvais. Mais pourquoi?

Autrement dit, React propose un attribut defaultProps, pour gérer les valeurs des accessoires par défaut. Et il est en fait préférable de l'utiliser quand il s'agit de composants non fonctionnels, car il sera appelé par toutes les méthodes qui s'appuient sur this.props.

L'extrait de code précédent crée de nouvelles variables locales nommées name et displayName, mais les valeurs par défaut sont appliquées à cette méthode render uniquement!. Si vous souhaitez que les valeurs par défaut soient appliquées à chaque méthode, telles que celles du cycle de vie React (shouldComponentUpdate, etc.), alors vous devez utilisez plutôt le defaultProps.

Ainsi, le code précédent est en fait une erreur qui peut conduire à des malentendus sur la valeur par défaut de name.

Voici comment il devrait être écrit à la place, pour obtenir le même comportement:

class MyComponent extends React.Component {
    render() {
        const { name, displayName = humanize(name), address } = this.props;
        return (
            <div>{displayName}</div>
          );
    }
}

MyComponent.defaultProps = {
    name: 'John Doe',
    address: helper.getDefaultAddress(),
};

C'est mieux. Parce que le nom sera toujours John Doe s'il n'a pas été défini. address la valeur par défaut a également été traitée, mais pas displayName... Pourquoi?

Eh bien, je n'ai pas encore trouvé de moyen de contourner ce cas d'utilisation spécial. Parce que displayName doit être basé sur la propriété name, à laquelle nous ne pouvons pas accéder (AFAIK) lors de la définition de defaultProps. La seule façon que je vois est de le traiter directement dans la méthode render. Il y a peut-être une meilleure façon.

Nous n'avons pas ce problème avec la propriété address car elle n'est pas basée sur les propriétés MyComponent mais s'appuie sur quelque chose de totalement indépendant qui n'a pas besoin des accessoires.

Composant non fonctionnel avec état

Il fonctionne exactement de la même manière que "Composant non fonctionnel sans état". Parce qu'il y a encore un cycle de vie, le comportement sera le même. Le fait qu'il existe un state interne supplémentaire dans le composant ne changera rien.


J'espère que cela aide à comprendre lors de l'utilisation de la déstructuration avec des composants. J'aime vraiment la façon fonctionnelle, c'est beaucoup plus propre à mon humble avis (+1 pour plus de simplicité).

Vous pouvez préférer toujours utiliser defaultProps, que vous travailliez avec des composants fonctionnels ou non fonctionnels, il est également valide. (+1 pour la cohérence)

Soyez juste conscient du cycle de vie avec des composants non fonctionnels qui "nécessite" l'utilisation de defaultProps. Mais au final, le choix vous appartient toujours;)

26
Vadorequest

En regardant le cas d'utilisation avancé que vous avez, vous ajoutez des propriétés inutiles au composant. label et placeholder dépendent de la transmission d'autres propriétés et, à mon avis, ne devraient pas être répertoriées en tant que paramètre du composant lui-même.

Si j'essayais d'utiliser <FormField /> dans mon application et j'avais besoin de regarder pour voir quelles dépendances ce composant spécifique, je serais un peu confus quant à la raison pour laquelle vous créez des paramètres basés sur d'autres paramètres. Je déplacerais les label et placeholder dans le corps de la fonction donc c'est clear ce ne sont pas des dépendances de composants mais simplement des effets secondaires.

En ce qui concerne les performances ici, je ne suis pas sûr qu'il y aurait une différence significative dans les deux sens. Les composants sans état n'ont pas vraiment d '"instance de support" que les composants avec état, ce qui signifie qu'il n'y a pas d'objet en mémoire gardant la trace du composant. Je crois que c'est juste une fonction pure de passer des paramètres et de renvoyer la vue.

Sur cette même note .. l'ajout des PropTypes aidera à la vérification du type.

const FormField = ({
  input: { name, value, ...inputRest },
  row = false,
  meta: { touched, error, warning },
  ...others,
}) => {
  const label = capitalize(name),
  const placeholder = label,

  return (
    // logic
  );
};

FormField.propTypes = {
  input: PropTypes.shape({
    name: PropTypes.string.isRequired,
    value: PropTypes.string,
  }).isRequired,
  meta: PropTypes.shape({
    touched: PropTypes.bool.isRequired,
    error: PropTypes.bool.isRequired,
    warning: PropTypes.bool.isRequired,
  }).isRequired,
  row: PropTypes.bool.isRequired,
};
1
Taylor Jones