30 votes

Les conteneurs react-redux connect() -ed peuvent-ils mettre en œuvre des méthodes de cycle de vie comme componentDidMount ?

Je suis tombé sur un modèle répété dans mon site react-redux : Un composant affiche des données provenant d'une api web, et celles-ci doivent être peuplées au chargement, automatiquement, sans aucune interaction de l'utilisateur.

Je veux lancer la récupération asynchrone à partir d'un composant conteneur, mais d'après ce que je sais, la seule façon de le faire est à partir d'un événement de cycle de vie dans un composant d'affichage. Cela semble rendre impossible le fait de placer toute la logique dans le conteneur et de n'utiliser que des composants fonctionnels apatrides muets pour l'affichage.

Cela signifie que je ne peux pas utiliser un composant fonctionnel sans état pour un composant qui a besoin de données asynchrones. Cela ne semble pas correct.

Il semble que la "bonne" façon de procéder serait d'initier des appels asynchrones à partir de l'interface utilisateur. conteneur . Ensuite, lorsque l'appel est renvoyé, l'état est mis à jour et le conteneur reçoit le nouvel état et le transmet à son tour à son composant sans état par l'intermédiaire de mapStateToProps() .

Faire des appels asynchrones dans mapStateToProps y mapDispatchToProps (j'entends par là le fait d'appeler la fonction asynchrone, par opposition au fait de la retourner en tant que propriété) n'a pas de sens.

Donc, ce que j'ai fini par faire, c'est de placer les appels asynchrones dans un fichier de type refreshData() exposée par mapDispatchToProps() puis de l'appeler à partir de deux ou plusieurs méthodes du cycle de vie de React : componentDidMount and componentWillReceiveProps .

Existe-t-il un moyen propre de mettre à jour l'état du magasin redux sans mettre des appels de méthode de cycle de vie dans chaque composant qui a besoin de données asynchrones ?

Devrais-je effectuer ces appels à un niveau supérieur de la hiérarchie des composants (ce qui réduirait la portée de ce problème, puisque seuls les composants de "premier niveau" devraient écouter les événements du cycle de vie) ?

Editar :

Pour qu'il n'y ait aucune confusion sur ce que j'entends par un composant de conteneur connect()ed, voici un exemple très simple :

import React from 'react';
import { connect } from 'react-redux';
import {action} from './actions.js';

import MyDumbComponent from './myDumbComponent.jsx';

function mapStateToProps(state)
{
  return { something: state.xxxreducer.something  };
}

function mapDispatchToProps(dispatch)
{
  return { 
       doAction: ()=>{dispatch(action())}
  };
}

const MyDumbComponentContainer = connect(
  mapStateToProps,
  mapDispatchToProps
)(MyDumbComponent);

// Uh... how can I hook into to componentDidMount()? This isn't 
// a normal React class.

export default MyDumbComponentContainer;

8voto

skypecakes Points 890

Jamie Dixon a écrit un paquet pour faire cela !

https://github.com/JamieDixon/react-lifecycle-component

L'utilisation ressemblerait à ceci :

const mapDispatchToProps = {
    componentDidMount: getAllTehDatas
}

...

export default connectWithLifecycle(mapStateToProps, mapDispatchToProps)(WrappedComponent)

7voto

jinglesthula Points 940

modifier Grâce aux hooks, vous êtes désormais en mesure de mettre en œuvre des callbacks de cycle de vie dans un composant fonctionnel sans état. Bien que cela ne réponde pas directement à tous les points de la question, cela peut également permettre de contourner certaines des raisons pour lesquelles on voulait faire ce qui était proposé à l'origine.


modifier la réponse originale Après la discussion dans les commentaires et en y réfléchissant davantage, cette réponse est plus exploratoire et peut servir d'élément de la conversation. Mais je ne pense pas que ce soit la bonne réponse.

première réponse

Sur le site de Redux, il y a un exemple qui montre que vous n'avez pas besoin de faire à la fois mapStateToProps et mapDispatchToProps. Vous pouvez simplement tirer parti de connect Pour les accessoires, utilisez une classe et implémentez les méthodes du cycle de vie sur le composant idiot.

Dans l'exemple, l'appel de connexion est même dans le même fichier et le composant muet n'est même pas exporté, donc pour l'utilisateur du composant c'est la même chose.

Je peux comprendre que l'on ne veuille pas faire d'appels asynchrones à partir du composant d'affichage. Je pense qu'il y a une distinction entre l'émission d'appels asynchrones à partir de là et la distribution d'une action qui, avec les thunks, déplace l'émission des appels asynchrones vers les actions (encore plus découplées du code React).

Par exemple, voici un composant d'écran d'accueil pour lequel j'aimerais effectuer une action asynchrone (comme le préchargement de ressources) lorsque le composant d'affichage se monte :

SplashContainer.js

import { connect } from 'react-redux'
import Splash from '../components/Splash'
import * as actions from '../actions'

const mapStateToProps = (state) => {
  return {
    // whatever you need here
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    onMount: () => dispatch(actions.splashMount())
  }
}

const SceneSplash = connect(
  mapStateToProps,
  mapDispatchToProps
)(Splash)

export default SceneSplash

Splash.js

import React from 'react'

class Splash extends React.Component {
  render() {
    return (
      <div className="scene splash">
      <span className="fa fa-gear fa-spin"></span>
      </div>
    )
  }

  componentDidMount() {
    const { onMount } = this.props
    onMount()
  }
}

export default Splash

Vous pouvez voir que le dispatch se fait dans le conteneur connecté, et vous pouvez imaginer dans le fichier actions.splashMount() nous émettons une requête http asynchrone ou faisons d'autres choses asynchrones par l'intermédiaire de thunks ou des promesses.

modifier pour clarifier

Permettez-moi d'essayer de défendre l'approche. J'ai relu la question et je ne suis pas sûr à 100 % de répondre à l'essentiel de ce qu'elle demande, mais soyez indulgent avec moi. Si je ne suis toujours pas tout à fait sur la bonne voie, je propose ci-dessous une approche modifiée qui pourrait être plus proche de la réalité.

"il doit être rempli au chargement" - l'exemple ci-dessus accomplit ceci

"Je veux lancer l'extraction asynchrone à partir d'un conteneur" - dans l'exemple, l'extraction n'est pas lancée à partir du composant d'affichage ou du conteneur, mais à partir d'une action asynchrone.

"Cela semble rendre impossible de mettre toute la logique dans le conteneur" - Je pense que vous pouvez toujours mettre toute logique supplémentaire nécessaire dans le conteneur. Comme indiqué, le code de chargement des données n'est pas dans le composant d'affichage (ou le conteneur) mais dans le créateur d'action asynchrone.

"Cela signifie que je ne peux pas utiliser un composant fonctionnel sans état pour un composant qui a besoin de données asynchrones." - Dans l'exemple ci-dessus, le composant d'affichage est apatride et fonctionnel. Le seul lien est la méthode de cycle de vie invoquant un callback. Il n'a pas besoin de savoir ou de se soucier de ce que fait cette callback. Il ne s'agit pas pour le composant d'affichage d'essayer d'être le propriétaire de la récupération de données asynchrones - il fait simplement savoir au code qui s'en occupe quand une chose particulière s'est produite.

Jusqu'à présent, j'essaie de justifier comment l'exemple donné répond aux exigences de la question. Cela dit, si ce que vous recherchez est d'avoir un composant d'affichage qui ne contient absolument aucun code lié au chargement asynchrone des données, même par des callbacks indirects - c'est-à-dire que le seul lien qu'il a est de consommer ces données via les props qui lui sont remis lorsque ces données distantes arrivent, alors je suggérerais quelque chose comme ceci :

SplashContainer.js

import { connect } from 'react-redux'
import Splash from '../components/Splash'
import * as actions from '../actions'

const mapStateToProps = (state) => {
  return {
    // whatever you need here
  }
}

const mapDispatchToProps = (dispatch) => {
  dispatch(actions.splashMount())
  return {
    // whatever else here may be needed
  }
}

const SceneSplash = connect(
  mapStateToProps,
  mapDispatchToProps
)(Splash)

export default SceneSplash

Splash.js

import React from 'react'

class Splash extends React.Component {
  // incorporate any this.props references here as desired
  render() {
    return (
      <div className="scene splash">
      <span className="fa fa-gear fa-spin"></span>
      </div>
    )
  }
}

export default Splash

En répartissant l'action dans mapDispatchToProps, vous laissez le code de cette action résider entièrement dans le conteneur. En fait, vous lancez l'appel asynchrone dès que le conteneur est instancié, plutôt que d'attendre que le composant d'affichage connecté tourne et soit monté. Cependant, si vous ne pouvez pas commencer l'appel asynchrone avant que le componentDidMount() du composant d'affichage ne se déclenche, je pense que vous êtes intrinsèquement lié à un code comme dans mon premier exemple.

Je n'ai pas encore testé cette deuxième approche pour voir si react ou redux s'en plaindront, mais cela devrait fonctionner. Vous avez accès à la méthode dispatch et devriez pouvoir l'appeler sans problème.

Pour être honnête, ce deuxième exemple, tout en supprimant tout le code lié à l'action asynchrone du composant d'affichage, me semble un peu étrange puisque nous faisons des choses qui ne sont pas liées à l'action asynchrone dans la fonction éponyme. Et les conteneurs n'ont pas vraiment de componentDidMount pour l'exécuter autrement. Donc je suis un peu nerveux avec ça et je pencherais pour la première approche. Ce n'est pas propre dans le sens "se sentir bien", mais ça l'est dans le sens "simple 1-liner".

0 votes

? Cette réponse fait exactement ce que ma question vise à éviter. Je ne suis pas sûr que vous ayez compris la question.

0 votes

@skypecakes :) Je ne suis pas sûr non plus. Je suis allée de l'avant et j'ai ajouté une modification à la réponse. Faites-moi savoir si je suis dans la bonne direction ou si je ne comprends toujours pas l'essentiel.

0 votes

Tu as mis le doigt sur la tête avec ça : "... les conteneurs n'ont pas vraiment de componentDidMount pour l'exécuter autrement." C'est la raison de cette question. Pourquoi ne le pourraient-ils pas ? Ce sont juste des composants React générés. Si le composant muet doit annoncer "Hey, j'ai été monté", cela semble contredire le modèle de flux de données à sens unique que react-redux semble être.

2voto

joshv Points 123

Consultez redux-saga https://github.com/yelouafi/redux-saga . Il s'agit d'un composant middleware redux qui crée des observateurs de longue durée qui recherchent des actions spécifiques du magasin et peuvent déclencher des fonctions ou des fonctions de générateur en réponse. La syntaxe du générateur est particulièrement adaptée à la gestion de l'asynchronisme, et redux-saga dispose de quelques aides intéressantes qui vous permettent de traiter le code asynchrone de manière synchrone. Voir certains de leurs exemples. https://github.com/yelouafi/redux-saga/blob/master/examples/async/src/sagas/index.js . La syntaxe du générateur peut être difficile à appréhender au début, mais d'après notre expérience, cette syntaxe prend en charge une logique asynchrone extrêmement complexe, y compris le débordement, l'annulation et la jonction/le suivi de plusieurs requêtes.

0 votes

Cela semble fantastique. Je continue à voir des mentions de redux-saga, je vais vérifier !

0 votes

J'utilise saga, mais je ne vois toujours pas comment cela répond à la question ?

1 votes

Si vous utilisez redux-saga ou vraiment toute autre bonne solution asynchrone pour redux, vous n'avez pas besoin d'utiliser les méthodes de cycle de vie pour faire le fetching. Nous avons des sagas qui sont importées dans les composants de l'interface utilisateur et qui vont récupérer un élément de données particulier, par exemple une liste de valeurs valides, et le placer dans le magasin. Ensuite, le composant IU se connecte à cette partie du magasin.

1voto

PositiveGuy Points 3245

Vous pouvez le faire à partir d'un conteneur. Il suffit de créer un composant qui étend React.Component et de le nommer avec "Container" quelque part dans le nom. Puis utilisez ce du conteneur componentDidMount au lieu d'utiliser componentDidMount dans le composante de présentation (muette) que le composant du conteneur rend. Le réducteur verra que vous avez encore envoyé une action, et mettra toujours à jour l'état de sorte que votre composant idiot sera en mesure d'accéder à ces données

Je fais du TDD, mais même si je n'en faisais pas, je sépare mes composants muets de ceux du conteneur via un fichier. Je déteste avoir trop de choses dans un seul fichier, surtout si l'on mélange les éléments dumb vs. container dans le même fichier, c'est le bordel. Je sais que des gens le font, mais je pense que c'est affreux.

Je fais ça :

src/components/someDomainFolder/someComponent.js (composante muette) src/components/someDomainFolder/someComponentContainer.js (par exemple, vous pourriez utiliser React-Redux ont connecté conteneur pas un composant de présentation connecté et donc dans someComponentContainer.js vous avez bien une classe react dans ce fichier, comme indiqué, il suffit de l'appeler someComponentContainer extends React.Component par exemple.

Vos fonctions mapStateToProps() et mapDispatchToProps() seraient des fonctions globales du composant conteneur connecté en dehors de la classe du conteneur. Et connect() rendrait le conteneur, qui rendrait le composant de présentation, mais cela vous permet de garder tout votre comportement dans votre fichier de conteneur, loin du code du composant de présentation idiot.

de cette façon, vous avez des tests autour d'un composant qui sont basés sur la structure/état et vous avez des tests de comportement autour du composant conteneur. Il est bien plus judicieux de maintenir et d'écrire des tests, de maintenir et de rendre les choses faciles pour vous-même ou pour d'autres développeurs afin de voir ce qui se passe et de gérer les composants muets et comportementaux.

En procédant de cette manière, vos éléments de présentation sont séparés à la fois physiquement par fichier ET par convention de code. ET vos tests sont regroupés autour des bonnes zones de code... et non dans un fouillis. ET si vous faites cela, et utilisez un réducteur qui écoute les mises à jour d'état, votre composant de présentation peut rester totalement stupide.... et juste rechercher les mises à jour d'état via les props... puisque vous utilisez mapStateToProps().

0 votes

Enveloppez donc le composant idiot dans une classe de composant React qui existe uniquement pour déclencher les méthodes du cycle de vie. Ce n'est pas une mauvaise idée. C'est à peu près ce que fait react-lifecycle-component, sauf que vous n'avez pas à écrire tout le code passe-partout pour la classe wrapper car la bibliothèque le fait pour vous. Au lieu de cela, vous pouvez simplement définir vos méthodes de cycle de vie dans mapDispatchToProps(). Mais je pourrais voir l'intérêt de séparer vos méthodes de cycle de vie dans un fichier séparé comme vous le faites.

2 votes

Quelques commentaires pour votre réponse : Un exemple de code complet serait plus facile à lire qu'un texte de description, à mon avis.

0 votes

Je vous recontacterai, je suis très occupé ce mois-ci.

0voto

Josh Harris Points 1

Suite à la suggestion de @PositiveGuy, voici un exemple de code permettant d'implémenter un composant conteneur qui peut utiliser les méthodes de cycle de vie. Je pense que c'est une approche assez propre qui maintient la séparation des préoccupations en gardant le composant de présentation "muet" :

import React from 'react';
import { connect } from 'react-redux'
import { myAction } from './actions/my_action_creator'
import MyPresentationComponent from './my_presentation_component'

const mapStateToProps = state => {
  return {
    myStateSlice: state.myStateSlice
  }
}

const mapDispatchToProps = dispatch => {
  return {
    myAction: () => {
      dispatch(myAction())
    }
  }
}

class Container extends React.Component {
  componentDidMount() {
    //You have lifecycle access now!!
  }

  render() {
    return(
      <MyPresentationComponent
        myStateSlice={this.props.myStateSlice}
        myAction={this.props.myAction}
      />
    )
  }
}

const ContainerComponent = connect(
  mapStateToProps,
  mapDispatchToProps
)(Container)

export default ContainerComponent

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X