web-dev-qa-db-fra.com

ReactJS - Levée de l'état vs maintien d'un état

Dans mon entreprise, nous sommes en train de migrer le frontal d’une application Web vers ReactJS . Nous travaillons avec create-react-app (mise à jour vers la version 16), sans Redux . Je suis maintenant bloqué sur une page. quelle structure peut être simplifiée par l'image suivante:

 Page structure

Les données affichées par les trois composants (SearchableList, SelectableList et Map) sont récupérées avec la même demande d'arrière-plan dans la méthode componentDidMount() de MainContainer. Le résultat de cette requête est ensuite stocké dans l'état de MainContainer et a une structure plus ou moins semblable à celle-ci:

state.allData = {
  left: {
    data: [ ... ]
  },
  right: {
    data: [ ... ],
    pins: [ ... ]
  }
}

LeftContainer reçoit en tant que prop state.allData.left de MainContainer et transmet props.left.data à SearchableList, à nouveau en tant que prop.

RightContainer reçoit en tant que prop state.allData.right de MainContainer et transmet props.right.data à SelectableList et props.right.pins à Map.

SelectableList affiche une case à cocher pour autoriser les actions sur ses éléments. Chaque fois qu'une action survient sur un élément du composant SelectableList, elle peut avoir des effets secondaires sur les punaises de la carte.

J'ai décidé de stocker dans l'état de RightContainer une liste qui conserve tous les identifiants des éléments affichés par SelectableList; cette liste est transmise comme accessoire à SelectableList et à Map. Ensuite, je passe un rappel à SelectableList, qui chaque fois qu'une sélection est faite met à jour la liste des identifiants dans RightContainer; les nouveaux accessoires arrivent à la fois dans SelectableList et Map, et donc render() est appelé dans les deux composants.

Cela fonctionne bien et permet de garder tout ce qui peut arriver à SelectableList et Map dans RightContainer, mais je demande si cela est correct pour lifting-state-up et single-source-of-truth concepts.

Comme alternative réalisable, j'ai pensé à ajouter une propriété _selected à chaque élément dans state.right.data dans MainContainer et à passer les trois niveaux de rappel sélectionné à SelectableList, en gérant toutes les actions possibles dans MainContainer. Mais dès qu'un événement de sélection se produit, cela finira par forcer le chargement de LeftContainer et de RightContainer, ce qui impliquera la nécessité d'implémenter des logiques telles que shouldComponentUpdate() pour éviter des render() inutiles, en particulier dans LeftContainer.

Quelle est/pourrait être la meilleure solution pour optimiser cette page du point de vue de l’architecture et de la performance?

Vous trouverez ci-dessous un extrait de mes composants pour vous aider à comprendre la situation.

MainContainer.js

class MainContainer extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      allData: {}
    };
  }

  componentDidMount() {
    fetch( ... )
      .then((res) => {
        this.setState({
          allData: res
        });
      });
  }

  render() {
    return (
      <div className="main-container">
        <LeftContainer left={state.allData.left} />
        <RightContainer right={state.allData.right} />
      </div>
    );
  }
}

export default MainContainer;

RightContainer.js

class RightContainer extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      selectedItems: [ ... ]
    };
  }

  onDataSelection(e) {
    const itemId = e.target.id;
    // ... handle itemId and selectedItems ...
  }

  render() {
    return (
      <div className="main-container">
        <SelectableList
          data={props.right.data}
          onDataSelection={e => this.onDataSelection(e)}
          selectedItems={this.state.selectedItems}
        />
        <Map
          pins={props.right.pins}
          selectedItems={this.state.selectedItems}
        />
      </div>
    );
  }
}

export default RightContainer;

Merci d'avance!

16
LucioB

Etat de la documentation

Souvent, plusieurs composants doivent refléter les mêmes données changeantes. Nous recommande de lever l’état partagé jusqu’à leur point commun le plus proche ancêtre.

Il devrait exister une "source de vérité" unique pour toutes les données qui changent dans une application React. D'habitude, l'état est d'abord ajouté au composant qui en a besoin pour le rendu. Ensuite, si les autres composants aussi besoin, vous pouvez le lever à leur plus proche ancêtre commun. Au lieu d'essayer de synchroniser l'état entre différents composants, vous devriez compter sur le flux de données descendant.

L’état de levage implique l’écriture de davantage de code «passe-partout» que de code à deux voies approches contraignantes, mais comme avantage, il faut moins de travail pour trouver et isoler les bugs. Depuis tout état "vit" dans un composant et que seul composant peut le changer, la surface pour les insectes est considérablement réduit. En outre, vous pouvez implémenter toute logique personnalisée pour rejeter ou transformer l'entrée utilisateur.

Il faut donc essentiellement élever les états de l’arbre utilisés par le composant Siblings. Donc, la première implémentation dans laquelle vous stockez la variable selectedItems en tant qu'état dans la variable RightContainer est tout à fait justifiée et constitue une bonne approche, car le parent n'a pas besoin de savoir et que cette variable data est en train d'être partagé par les deux composants child de RightContainer et ces deux ont maintenant une source unique de vérité.

Selon votre question:

Comme alternative possible, j'ai pensé à ajouter une propriété _selected à chaque élément dans state.right.data dans MainContainer et transmettez le résultat sélectionné rappeler trois niveaux jusqu'à SelectableList, en gérant tous les fichiers actions possibles dans MainContainer

Je ne serais pas d'accord pour dire que c'est une meilleure approche que la première, puisque vous MainContainer n'avez pas besoin de connaître la selectedItems ou le gestionnaire des mises à jour. MainContainer ne fait rien à propos de ces états et ne fait que le transmettre. 

Pensez à optimise on performance, vous parlez vous-même de l'implémentation de shouldComponentUpdate, mais vous pouvez éviter cela en créant vos composants en développant React.PureComponent qui implémente essentiellement le shouldComponentUpdate avec une comparaison shallow de state et props.

Selon les docs:

Si la fonction render() de votre composant React donne le même résultat étant donné les mêmes accessoires et le même état, vous pouvez utiliser React.PureComponent pour un amélioration de la performance dans certains cas.

Toutefois, si plusieurs composants profondément imbriqués utilisent les mêmes données, il est judicieux d'utiliser Redux et de stocker ces données dans l'état redux. De cette manière, il est globalement accessible à l’ensemble de l’application et peut être partagé entre des composants qui ne sont pas directement liés.

Par exemple, considérons le cas suivant

const App = () => {
    <Router>
         <Route path="/" component={Home}/>
         <Route path="/mypage" component={MyComp}/>
    </Router>
}

Maintenant, voici si Home et MyComp veulent accéder aux mêmes données. Vous pouvez transmettre les données sous forme d'accessoires d'App en les appelant par l'intermédiaire de render prop. Cependant, cela serait facilement fait en connectant ces deux composants à l’état Redux en utilisant une fonction connect comme

const mapStateToProps = (state) => {
   return {
      data: state.data
   }
}

export connect(mapStateToProps)(Home);

et de même pour MyComp. Aussi, il est facile de configurer des actions pour mettre à jour les informations pertinentes

En outre, il est particulièrement facile de configurer Redux pour votre application et vous seriez en mesure de stocker des données relatives aux mêmes choses dans les réducteurs individuels. De cette façon, vous seriez en mesure de modulariser vos données d'application aussi bien

8
Shubham Khatri

Mon conseil honnête à ce sujet. De l'expérience est:

Redux est simple. C'est facile à comprendre et à mettre à l'échelle MAIS vous devriez utiliser Redux pour certains cas d'utilisation spécifiques.

Depuis que Redux encapsule votre application, vous pouvez penser à stocker des éléments tels que:

  • locale actuelle de l'application
  • utilisateur authentifié actuel
  • jeton actuel de quelque part

Des choses dont vous auriez besoin à l'échelle mondiale. react-redux permet même un @connect decorator sur les composants. Alors comme:

@connect(state => ({ 
   locale: state.locale,
   currentUser: state.currentUser
}))
class App extends React.Component

Ceux-ci sont tous transmis comme accessoires et connect peuvent être utilisés n'importe où sur l'application. Bien que je recommande de ne transmettre que les accessoires mondiaux à l'opérateur de diffusion

<Navbar {...this.props} />

Tous les autres composants (ou "pages") de votre application peuvent créer leur propre état encapsulé. Par exemple, la page Utilisateurs peut faire sa propre chose.

class Users extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      loadingUsers: false,
      users: [],
    };
  }
......

Vous accéderiez aux paramètres régionaux et currentUser via des accessoires, car ils étaient transmis des composants Container.

Cette approche je l'ai fait plusieurs fois et ça marche.

Mais , puisque vous vouliez vraiment consolider d'abord la connaissance de React, avant de faire Redux, vous pouvez simplement stocker votre état sur le composant de niveau supérieur et le transmettre aux enfants.

Inconvénients:

  • Tu vas devoir continuer à les transmettre en composants de niveau interne
  • Pour mettre à jour l'état des composants de niveau interne, vous devez passer la fonction qui met à jour l'état.

Ces inconvénients sont un peu ennuyeux et lourds à gérer. C'est pourquoi Redux a été construit.

J'espère que j'ai aidé. bonne chance

6
Lokuzt

En utilisant Redux, vous pouvez éviter de tels rappels et conserver l’ensemble de l’état dans un seul magasin. Créez donc votre composant connecté en tant que composant parent. ne vous inquiétez pas des rappels dans ce cas.

0
monkeyjs