État ReactJS vs prop


121

Cela peut marcher sur cette ligne entre responsable et opiniâtre, mais je vais et vient sur la façon de structurer un composant ReactJS à mesure que la complexité augmente et pourrait utiliser une certaine direction.

Venant d'AngularJS, je souhaite transmettre mon modèle au composant en tant que propriété et demander au composant de modifier directement le modèle. Ou devrais-je diviser le modèle en différentes statepropriétés et le recompiler lors du renvoi en amont? Quelle est la manière ReactJS?

Prenons l'exemple d'un éditeur d'articles de blog. Essayer de modifier le modèle finit directement par ressembler à:

var PostEditor = React.createClass({
  updateText: function(e) {
    var text = e.target.value;
    this.props.post.text = text;
    this.forceUpdate();
  },
  render: function() {
    return (
      <input value={this.props.post.text} onChange={this.updateText}/>
      <button onClick={this.props.post.save}/>Save</button>
    );
  }
});

Ce qui semble faux.

Est-ce plus la manière React de créer notre textpropriété de modèle stateet de la recompiler dans le modèle avant de l'enregistrer comme:

var PostEditor = React.createClass({
  getInitialState: function() {
    return {
      text: ""
    };
  },
  componentWillMount: function() {
    this.setState({
      text: this.props.post.text
    });
  },
  updateText: function(e) {
    this.setState({
      text: e.target.value
    });
  },
  savePost: function() {
    this.props.post.text = this.state.text;
    this.props.post.save();
  },
  render: function() {
    return (
      <input value={this.state.text} onChange={this.updateText}/>
      <button onClick={this.savePost}/>Save</button>
    );
  }
});

Cela ne nécessite pas d'appel this.forceUpdate(), mais au fur et à mesure que le modèle grandit (un article peut avoir un auteur, un sujet, des balises, des commentaires, des notes, etc.), le composant commence à devenir vraiment compliqué.

La première méthode avec ReactLink est-elle la voie à suivre?

Réponses:


64

Votre deuxième approche lui ressemble plus. React ne se soucie pas tant des modèles que des valeurs et de la manière dont elles circulent dans votre application. Idéalement, votre modèle de publication serait stocké dans un seul composant à la racine. Vous créez ensuite des composants enfants qui consomment chacun des parties du modèle.

Vous pouvez transmettre des rappels aux enfants qui doivent modifier vos données et les appeler à partir du composant enfant.

Modifier directement this.props ou this.state n'est pas une bonne idée, car React ne pourra pas prendre en compte les changements. C'est parce que React effectue une comparaison superficielle de votre accessoire de publication pour déterminer s'il a changé.

J'ai créé ce jsfiddle pour montrer comment les données pouvaient circuler d'un composant externe vers un composant interne.

La handleClickméthode montre 3 façons de (im) mettre à jour correctement l'état:

var Outer = React.createClass({

  getInitialState: function() {
    return {data: {value: 'at first, it works'}};
  },

  handleClick: function () {

    // 1. This doesn't work, render is not triggered.
    // Never set state directly because the updated values
    // can still be read, which can lead to unexpected behavior.

    this.state.data.value = 'but React will never know!';

    // 2. This works, because we use setState

    var newData = {value: 'it works 2'};
    this.setState({data: newData});

    // 3. Alternatively you can use React's immutability helpers
    // to update more complex models.
    // Read more: http://facebook.github.io/react/docs/update.html

    var newState = React.addons.update(this.state, {
      data: {value: {$set: 'it works'}}
    });
    this.setState(newState);
 },

  render: function() {
      return <Inner data={this.state.data} handleClick={this.handleClick} />;
  }
});

Mais que faisons-nous si nous avons un modèle opaque, avec ses propres fonctions de manipulation d'état? Par exemple, supposons qu'au lieu d'un textchamp, nous ayons une setText méthode qui effectue la validation et d'autres choses. Je peux voir la méthode (2) fonctionner si elle setTextest pure et renvoie une toute nouvelle instance du modèle. Cependant, si setTextnous mettions simplement à jour l'état interne, nous aurions encore besoin d'appeler forceUpdate, non?
hugomg

1
Oui, vous pouvez appeler forceUpdate, mais à ce moment-là, vous «fuyez» de React. Il peut être préférable de transmettre un setState()rappel au modèle opaque pour éviter d'avoir à déclencher manuellement des rendus.
jxg

Je ne suis toujours pas sûr de bien comprendre. Donc, tout composant destiné à modifier les données doit faire une copie complète des accessoires passés? Puis modifier et envoyer cette copie en amont pour ne pas modifier les données d'origine? Finalement, le changement ira jusqu'à la racine, là où il est traité, et toute l'application est rendue? Est-ce correct?
nicholas

97

Mise à jour 2016: React est changé, et l'explication "props vs state" est devenue très simple. Si un composant a besoin de modifier des données - mettez-le dans un état, sinon dans des accessoires. Parce que les accessoires sont désormais en lecture seule .

Quelle est la différence exacte entre les accessoires et l'état?

Vous pouvez trouver une bonne explication ici (version complète)

Changer les accessoires et l'état


1
en fait setProps () pourrait changer les accessoires à l'intérieur d'un composant et déclencher un nouveau rendu
WaiKit Kung

2
setPropsest obsolète et ne doit pas être utilisé. Le remplacement consiste à rendre le composant et à laisser React gérer les différences.
jdmichal

Et si vous cherchez une vidéo explicative: youtube.com/watch?v=qh3dYM6Keuw
jaisonDavis

35

De React doc

Les accessoires sont immuables: ils sont passés par le parent et sont «possédés» par le parent. Pour implémenter les interactions, nous introduisons un état mutable dans le composant. this.state est privé pour le composant et peut être modifié en appelant this.setState (). Lorsque l'état est mis à jour, le composant se restitue.

Depuis TrySpace : lorsque les accessoires (ou l'état) sont mis à jour (via setProps / setState ou parent), le composant est également restitué.


16

Une lecture de Thinking in React :

Passons en revue chacun d'eux et découvrons lequel est l'état. Posez simplement trois questions sur chaque donnée:

  1. Est-il transmis par un parent via des accessoires? Si c'est le cas, ce n'est probablement pas un état.
  2. Cela change-t-il avec le temps? Sinon, ce n'est probablement pas un état.

  3. Pouvez-vous le calculer en fonction de tout autre état ou accessoire de votre composant? Si c'est le cas, ce n'est pas un état.


13

Je ne sais pas si je réponds à votre question, mais j'ai trouvé que, en particulier dans une application de grande taille / en croissance, le modèle Conteneur / Composant fonctionne incroyablement bien.

Vous avez essentiellement deux composants React:

  • un composant d'affichage «pur», qui traite du style et de l'interaction DOM;
  • un composant conteneur, qui traite de l'accès / sauvegarde des données externes, de la gestion de l'état et du rendu du composant d'affichage.

Exemple

NB Cet exemple est probablement trop simple pour illustrer les avantages de ce modèle, car il est assez détaillé pour un cas aussi simple.

/**
 * Container Component
 *
 *  - Manages component state
 *  - Does plumbing of data fetching/saving
 */

var PostEditorContainer = React.createClass({
  getInitialState: function() {
    return {
      text: ""
    };
  },

  componentWillMount: function() {
    this.setState({
      text: getPostText()
    });
  },

  updateText: function(text) {
    this.setState({
      text: text
    });
  },

  savePost: function() {
    savePostText(this.state.text);
  },

  render: function() {
    return (
      <PostEditor
        text={this.state.text}
        onChange={this.updateText.bind(this)}
        onSave={this.savePost.bind(this)}
      />
    );
  }
});


/**
 * Pure Display Component
 *
 *  - Calculates styling based on passed properties
 *  - Often just a render method
 *  - Uses methods passed in from container to announce changes
 */

var PostEditor = React.createClass({
  render: function() {
    return (
      <div>
        <input type="text" value={this.props.text} onChange={this.props.onChange} />
        <button type="button" onClick={this.props.onSave} />
      </div>
    );
  }
});

Avantages

En séparant la logique d'affichage et la gestion des données / états, vous disposez d'un composant d'affichage réutilisable qui:

  • peut facilement être itéré avec différents ensembles d'accessoires en utilisant quelque chose comme react-component-terrain de jeu
  • peut être enveloppé avec un conteneur différent pour un comportement différent (ou combiné avec d'autres composants pour créer de plus grandes parties de votre application

Vous disposez également d'un composant conteneur qui gère toutes les communications externes. Cela devrait vous permettre d'être plus flexible sur la manière dont vous accédez à vos données si vous apportez des modifications sérieuses ultérieurement *.

Ce modèle rend également l'écriture et la mise en œuvre de tests unitaires beaucoup plus simples.

Après avoir répété plusieurs fois une grande application React, j'ai constaté que ce modèle rend les choses relativement indolores, en particulier lorsque vous avez des composants plus volumineux avec des styles calculés ou des interactions DOM compliquées.

* Lisez le modèle de flux et jetez un œil à Marty.js , qui a largement inspiré cette réponse (et j'utilise beaucoup ces derniers temps) Redux (et react-redux ), qui implémentent extrêmement bien ce modèle.

Remarque pour ceux qui liront ceci en 2018 ou après:

React a beaucoup évolué depuis que cette réponse a été écrite, en particulier avec l'introduction de Hooks . Cependant, la logique de gestion d'état sous-jacente de cet exemple reste la même et, plus important encore, les avantages que vous obtenez en gardant votre état et la logique de présentation séparés s'appliquent toujours de la même manière.


0

Je pense que vous utilisez un anti-pattern que Facebook a déjà expliqué sur ce lien

Voici ce que vous trouvez:

React.createClass({
  getInitialState: function() {
    return { value: { foo: 'bar' } };
  },

  onClick: function() {
    var value = this.state.value;
    value.foo += 'bar'; // ANTI-PATTERN!
    this.setState({ value: value });
  },

  render: function() {
    return (
      <div>
        <InnerComponent value={this.state.value} />
        <a onClick={this.onClick}>Click me</a>
      </div>
    );
  }
});

La première fois que le composant interne est rendu, il aura {foo: 'bar'} comme valeur prop. Si l'utilisateur clique sur l'ancre, l'état du composant parent sera mis à jour à {value: {foo: 'barbar'}}, déclenchant le processus de re-rendu du composant interne, qui recevra {foo: 'barbar'} comme la nouvelle valeur de l'accessoire.

Le problème est que puisque le parent et les composants internes partagent une référence au même objet, lorsque l'objet subit une mutation sur la ligne 2 de la fonction onClick, l'accessoire du composant interne change. Ainsi, lorsque le processus de re-rendu démarre et que shouldComponentUpdate est appelé, this.props.value.foo sera égal à nextProps.value.foo, car en fait, this.props.value fait référence au même objet que nextProps.value.

Par conséquent, puisque nous manquerons le changement sur l'accessoire et court-circuiterons le processus de re-rendu, l'interface utilisateur ne sera pas mise à jour de 'bar' à 'barbar'


Pouvez-vous s'il vous plaît également poster le Innercomponentscode?
Abdullah Khan
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.