Comment utiliser la méthode du cycle de vie getDerivedStateFromProps par opposition à componentWillReceiveProps

142

Il semble componentWillReceivePropsqu'il sera complètement éliminé dans les prochaines versions, au profit d'une nouvelle méthode de cycle de vie getDerivedStateFromProps: static getDerivedStateFromProps () .

Après inspection, il semble que vous ne puissiez plus faire une comparaison directe entre this.propset nextProps, comme vous pouvez le faire componentWillReceiveProps. Y a-t-il un moyen de contourner cela?

En outre, il renvoie maintenant un objet. Ai-je raison de supposer que la valeur de retour est essentiellement this.setState?

Voici un exemple que j'ai trouvé en ligne: État dérivé des accessoires / état .

Avant

class ExampleComponent extends React.Component {
  state = {
    derivedData: computeDerivedState(this.props)
  };

  componentWillReceiveProps(nextProps) {
    if (this.props.someValue !== nextProps.someValue) {
      this.setState({
        derivedData: computeDerivedState(nextProps)
      });
    }
  }
}

Après

class ExampleComponent extends React.Component {
  // Initialize state in constructor,
  // Or with a property initializer.
  state = {};

  static getDerivedStateFromProps(nextProps, prevState) {
    if (prevState.someMirroredValue !== nextProps.someValue) {
      return {
        derivedData: computeDerivedState(nextProps),
        someMirroredValue: nextProps.someValue
      };
    }

    // Return null to indicate no change to state.
    return null;
  }
}
Andrew
la source

Réponses:

96

À propos de la suppression de componentWillReceiveProps : vous devriez être en mesure de gérer ses utilisations avec une combinaison de getDerivedStateFromPropset componentDidUpdate, voir le billet de blog React pour des exemples de migrations. Et oui, l'objet renvoyé par getDerivedStateFromPropsmet à jour l'état de la même manière qu'un objet passé à setState.

Au cas où vous auriez vraiment besoin de l'ancienne valeur d'un accessoire, vous pouvez toujours le mettre en cache dans votre état avec quelque chose comme ceci:

state = {
  cachedSomeProp: null
  // ... rest of initial state
};

static getDerivedStateFromProps(nextProps, prevState) {
  // do things with nextProps.someProp and prevState.cachedSomeProp
  return {
    cachedSomeProp: nextProps.someProp,
    // ... other derived state properties
  };
}

Tout ce qui n'affecte pas l'état peut être mis en place componentDidUpdate, et il y a même un getSnapshotBeforeUpdatetruc de très bas niveau.

MISE À JOUR: Pour avoir une idée des nouvelles (et anciennes) méthodes de cycle de vie, le package react-lifecycle-visualizer peut être utile.

Oblosys
la source
1
Ugh, j'ai foiré la question. Je voulais vraiment direcomponentWillReceiveProps
Andrew
2
J'avais pensé à utiliser mon état pour contenir les accessoires précédents, mais je voulais vraiment éviter le code et la logique supplémentaires nécessaires pour l'implémenter. Je vais examiner certaines des autres choses que vous soulevez. Merci beaucoup!
Andrew
4
Le fait de devoir stocker un accessoire précédent dans un état n'est qu'une solution de contournement standard pour ce changement d'API React difficile à comprendre. Pour les yeux de nombreux développeurs, cela ressemble à un changement d'anti-modèle et de régression. Pas vous critiquer Oblosys, mais l'équipe React.
AxeEffect
2
@AxeEffect C'est parce qu'il getDerivedStateFromPropsn'a jamais été vraiment destiné à la mémorisation . Veuillez voir ma réponse ci-dessous où j'ai plutôt décrit l'approche recommandée .
Dan Abramov
est-ce une faute de frappe? Avez-vous manqué ...? C'est-à-dire que nous devons renvoyer l'objet d'état entier ou seulement la partie qui nous intéresse.
theprogrammer
51

Comme nous l' avons récemment sur le blog de React , dans la grande majorité des cas , vous n'avez pas besoin getDerivedStateFromPropsdu tout .

Si vous souhaitez simplement calculer des données dérivées, soit:

  1. Faites-le directement à l'intérieur render
  2. Ou, si le recalculer coûte cher, utilisez un assistant de mémorisation comme memoize-one.

Voici l'exemple "après" le plus simple:

import memoize from "memoize-one";

class ExampleComponent extends React.Component {
  getDerivedData = memoize(computeDerivedState);

  render() {
    const derivedData = this.getDerivedData(this.props.someValue);
    // ...
  }
}

Consultez cette section du billet de blog pour en savoir plus.

Dan Abramov
la source
45
Si ce n'est pas nécessaire dans la grande majorité des cas, je suis surpris que ce soit un changement si nécessaire, celui qui brisera des milliers de projets en cours. On dirait que l'équipe React a commencé l'ingénierie.
Ska
39
Changez de componentWillReceiveProps en getDerivedStateFromProps. Ce n'est pas une rupture mais oblige à refactoriser tout le code existant, ce qui prend beaucoup de temps. Et cela semble très peu avantageux puisque vous dites que vous ne devriez pas l'utiliser du tout dans la grande majorité des cas. Pourquoi passer par les tracas de changer l'API pour quelque chose qui ne devrait même pas être utilisé en premier lieu.
Ska
4
J'aimerais une réponse à ce commentaire de Dan Abramov.
Louis345
6
@DanAbramov une réponse sur la raison de ce changement?
Petros Kyriakou
3
En fait, dans nos projets, cela est beaucoup utilisé. Pour afficher des choses comme des barres-collations sur les écrans lorsque de nouvelles données arrivent, 1 exemple. componentWillReceivePropsétait simple et cela a fonctionné. Pourquoi le supprimer pour cette poubelle statique ...
Oliver Dixon
6

Tel que mentionné par Dan Abramov

Faites-le directement à l'intérieur du rendu

Nous utilisons en fait cette approche avec memoise one pour tout type d'accessoires de proxy pour les calculs d'état.

Notre code ressemble à ceci

// ./decorators/memoized.js  
import memoizeOne from 'memoize-one';

export function memoized(target, key, descriptor) {
  descriptor.value = memoizeOne(descriptor.value);
  return descriptor;
}

// ./components/exampleComponent.js
import React from 'react';
import { memoized } from 'src/decorators';

class ExampleComponent extends React.Component {
  buildValuesFromProps() {
    const {
      watchedProp1,
      watchedProp2,
      watchedProp3,
      watchedProp4,
      watchedProp5,
    } = this.props
    return {
      value1: buildValue1(watchedProp1, watchedProp2),
      value2: buildValue2(watchedProp1, watchedProp3, watchedProp5),
      value3: buildValue3(watchedProp3, watchedProp4, watchedProp5),
    }
  }

  @memoized
  buildValue1(watchedProp1, watchedProp2) {
    return ...;
  }

  @memoized
  buildValue2(watchedProp1, watchedProp3, watchedProp5) {
    return ...;
  }

  @memoized
  buildValue3(watchedProp3, watchedProp4, watchedProp5) {
    return ...;
  }

  render() {
    const {
      value1,
      value2,
      value3
    } = this.buildValuesFromProps();

    return (
      <div>
        <Component1 value={value1}>
        <Component2 value={value2}>
        <Component3 value={value3}>
      </div>
    );
  }
}

Les avantages de celui-ci sont que vous n'avez pas besoin de coder des tonnes de passe-partout de comparaison à l'intérieur getDerivedStateFromPropsou componentWillReceivePropset que vous pouvez ignorer l'initialisation par copier-coller dans un constructeur.

REMARQUE:

Cette approche est utilisée uniquement pour transmettre par proxy les accessoires à l'état, au cas où vous auriez une logique d'état interne, elle doit encore être gérée dans les cycles de vie des composants.

mpospelov
la source