ReactJS Deux composants communiquent


321

Je viens de commencer avec ReactJS et je suis un peu coincé sur un problème que j'ai.

Mon application est essentiellement une liste avec des filtres et un bouton pour changer la mise en page. En ce moment , j'utilise trois composantes: <list />, < Filters />et <TopBar />, maintenant évidemment quand je change les paramètres dans < Filters />je veux déclencher une méthode <list />pour mettre à jour mon avis.

Comment puis-je faire interagir ces 3 composants entre eux, ou ai-je besoin d'une sorte de modèle de données global sur lequel je peux simplement apporter des modifications?


Les trois composantes de la fratrie sont-elles l'une ou l'autre dans l'autre?
pgreen2

Ce sont tous les trois composants, j'ai déjà réorganisé ma demande afin qu'ils aient maintenant tous le même parent qui peut leur fournir des données.
woutr_be du

4
C'est là que vous pouvez utiliser le flux ou le modèle de pubsub. Sur la base des documents dans les documents react, ils laissent une phrase quelque peu ambiguë: "Pour la communication entre deux composants qui n'ont pas de relation parent-enfant, vous pouvez configurer votre propre système d'événements global." facebook.github.io/react/tips/…
BingeBoy

@BingeBoy a raison Flux est un excellent moyen d'écrire des applications réactives, qui peuvent gérer le problème de flux de données, de partage de données par de nombreux composants.
Ankit Patial

4
Si vous ne voulez pas entrer dans Flux ou Redux, ceci est un article génial sur ce sujet andrewhfarmer.com/component-communication
garajo

Réponses:


318

La meilleure approche dépend de la façon dont vous prévoyez d'organiser ces composants. Quelques exemples de scénarios qui vous viennent à l'esprit en ce moment:

  1. <Filters /> est un composant enfant de <List />
  2. Les deux <Filters />et <List />sont les enfants d'un composant parent
  3. <Filters />et <List />vivent entièrement dans des composants racine séparés.

Il peut y avoir d'autres scénarios auxquels je ne pense pas. Si le vôtre ne rentre pas dans ces limites, faites-le moi savoir. Voici quelques exemples très approximatifs de la façon dont j'ai géré les deux premiers scénarios:

Scénario 1

Vous pouvez passer un gestionnaire de <List />à <Filters />, qui pourrait ensuite être appelé sur l' onChangeévénement pour filtrer la liste avec la valeur actuelle.

JSFiddle pour # 1 →

/** @jsx React.DOM */

var Filters = React.createClass({
  handleFilterChange: function() {
    var value = this.refs.filterInput.getDOMNode().value;
    this.props.updateFilter(value);
  },
  render: function() {
    return <input type="text" ref="filterInput" onChange={this.handleFilterChange} placeholder="Filter" />;
  }
});

var List = React.createClass({
  getInitialState: function() {
    return {
      listItems: ['Chicago', 'New York', 'Tokyo', 'London', 'San Francisco', 'Amsterdam', 'Hong Kong'],
      nameFilter: ''
    };
  },
  handleFilterUpdate: function(filterValue) {
    this.setState({
      nameFilter: filterValue
    });
  },
  render: function() {
    var displayedItems = this.state.listItems.filter(function(item) {
      var match = item.toLowerCase().indexOf(this.state.nameFilter.toLowerCase());
      return (match !== -1);
    }.bind(this));

    var content;
    if (displayedItems.length > 0) {
      var items = displayedItems.map(function(item) {
        return <li>{item}</li>;
      });
      content = <ul>{items}</ul>
    } else {
      content = <p>No items matching this filter</p>;
    }

    return (
      <div>
        <Filters updateFilter={this.handleFilterUpdate} />
        <h4>Results</h4>
        {content}
      </div>
    );
  }
});

React.renderComponent(<List />, document.body);

Scénario # 2

Similaire au scénario n ° 1, mais le composant parent sera celui qui transmettra la fonction de gestionnaire à <Filters />, et passera la liste filtrée à <List />. J'aime mieux cette méthode car elle dissocie le <List />du <Filters />.

JSFiddle pour # 2 →

/** @jsx React.DOM */

var Filters = React.createClass({
  handleFilterChange: function() {
    var value = this.refs.filterInput.getDOMNode().value;
    this.props.updateFilter(value);
  },
  render: function() {
    return <input type="text" ref="filterInput" onChange={this.handleFilterChange} placeholder="Filter" />;
  }
});

var List = React.createClass({
  render: function() {
    var content;
    if (this.props.items.length > 0) {
      var items = this.props.items.map(function(item) {
        return <li>{item}</li>;
      });
      content = <ul>{items}</ul>
    } else {
      content = <p>No items matching this filter</p>;
    }
    return (
      <div className="results">
        <h4>Results</h4>
        {content}
      </div>
    );
  }
});

var ListContainer = React.createClass({
  getInitialState: function() {
    return {
      listItems: ['Chicago', 'New York', 'Tokyo', 'London', 'San Francisco', 'Amsterdam', 'Hong Kong'],
      nameFilter: ''
    };
  },
  handleFilterUpdate: function(filterValue) {
    this.setState({
      nameFilter: filterValue
    });
  },
  render: function() {
    var displayedItems = this.state.listItems.filter(function(item) {
      var match = item.toLowerCase().indexOf(this.state.nameFilter.toLowerCase());
      return (match !== -1);
    }.bind(this));

    return (
      <div>
        <Filters updateFilter={this.handleFilterUpdate} />
        <List items={displayedItems} />
      </div>
    );
  }
});

React.renderComponent(<ListContainer />, document.body);

Scénario # 3

Lorsque les composants ne peuvent pas communiquer entre une sorte de relation parent-enfant, la documentation recommande de configurer un système d'événements global .


6
La bonne chose avec # 2 est qu'ils ne dépendent que d'un parent qui passe un accessoire à chaque composant: une fonction quant updateFilterà <Filters />et un tableau quant itemsà <List />. Vous pouvez utiliser ces composants enfants chez d'autres parents avec des comportements différents, ensemble ou en solo. Par exemple, si vous souhaitez afficher une liste dynamique mais n'avez pas besoin de filtrage.
Michael LaCroix

2
@woutr_be Je ne sais pas si cela correspondrait à vos besoins, mais dans une situation similaire, nous avons utilisé les deux fonctions suivantes pour trier la communication entre les composants enfant et parent: - listenTo: function (eventName, eventCallback) {$ ( window.document) .bind (eventName, eventCallback);} triggerEvent: fonction (eventName, params) {$ .event.trigger (eventName, params);} J'espère que ça aide! (désolé, je
n'ai

29
Pour le scénario 3, existe-t-il une approche recommandée? Des documents ou des exemples à ce sujet en générant des événements synthétiques personnalisés? Je n'ai rien trouvé dans la documentation principale.
pwray

1
Scénario n ° 2 fait beaucoup de sens ... jusqu'à ce que vous devez mettre en péril la conception (si seulement, la mise en page) - alors vous rendez compte de la nécessité d'un EventHub / PubSub.
Cody

4
Le lien du scénario n ° 3 est mort et redirige maintenant vers une page de documents React sans rapport.
Beporter

170

Il existe plusieurs façons de faire communiquer les composants. Certains peuvent être adaptés à votre cas d'utilisation. Voici une liste de certains que j'ai trouvé utile de connaître.

Réagir

Communication directe parents / enfants

const Child = ({fromChildToParentCallback}) => (
  <div onClick={() => fromChildToParentCallback(42)}>
    Click me
  </div>
);

class Parent extends React.Component {
  receiveChildValue = (value) => {
    console.log("Parent received value from child: " + value); // value is 42
  };
  render() {
    return (
      <Child fromChildToParentCallback={this.receiveChildValue}/>
    )
  }
}

Ici, le composant enfant appellera un rappel fourni par le parent avec une valeur, et le parent pourra obtenir la valeur fournie par les enfants du parent.

Si vous créez une fonctionnalité / page de votre application, il est préférable qu'un seul parent gère les rappels / l'état (également appelé containerou smart component) et que tous les enfants soient apatrides, ne signalant que les choses au parent. De cette façon, vous pouvez facilement «partager» l'état du parent à tout enfant qui en a besoin.


Le contexte

React Context permet de maintenir l'état à la racine de la hiérarchie de vos composants et de pouvoir injecter facilement cet état dans des composants imbriqués très profondément, sans avoir à passer des accessoires à tous les composants intermédiaires.

Jusqu'à présent, le contexte était une fonctionnalité expérimentale, mais une nouvelle API est disponible dans React 16.3.

const AppContext = React.createContext(null)

class App extends React.Component {
  render() {
    return (
      <AppContext.Provider value={{language: "en",userId: 42}}>
        <div>
          ...
          <SomeDeeplyNestedComponent/>
          ...
        </div>
      </AppContext.Provider>
    )
  }
};

const SomeDeeplyNestedComponent = () => (
  <AppContext.Consumer>
    {({language}) => <div>App language is currently {language}</div>}
  </AppContext.Consumer>
);

Le consommateur utilise le modèle de fonction render prop / children

Consultez cet article de blog pour plus de détails.

Avant React 16.3, je recommanderais d'utiliser react-broadcast qui offre une API assez similaire et d'utiliser l'ancienne API de contexte.


Portails

Utilisez un portail lorsque vous souhaitez garder 2 composants proches l'un de l'autre pour les faire communiquer avec des fonctions simples, comme dans un parent / enfant normal, mais vous ne voulez pas que ces 2 composants aient une relation parent / enfant dans le DOM, car des contraintes visuelles / CSS qu'elle implique (comme le z-index, l'opacité ...).

Dans ce cas, vous pouvez utiliser un "portail". Il existe différentes bibliothèques React utilisant des portails , généralement utilisés pour les modaux , les popups, les info-bulles ...

Considérer ce qui suit:

<div className="a">
    a content
    <Portal target="body">
        <div className="b">
            b content
        </div>
    </Portal>
</div>

Pourrait produire le DOM suivant lors du rendu à l'intérieur reactAppContainer:

<body>
    <div id="reactAppContainer">
        <div className="a">
             a content
        </div>
    </div>
    <div className="b">
         b content
    </div>
</body>

Plus de détails ici


Machines à sous

Vous définissez un emplacement quelque part, puis vous remplissez l'emplacement à partir d'un autre endroit de votre arborescence de rendu.

import { Slot, Fill } from 'react-slot-fill';

const Toolbar = (props) =>
  <div>
    <Slot name="ToolbarContent" />
  </div>

export default Toolbar;

export const FillToolbar = ({children}) =>
  <Fill name="ToolbarContent">
    {children}
  </Fill>

C'est un peu similaire aux portails, sauf que le contenu rempli sera rendu dans un emplacement que vous définissez, tandis que les portails rendent généralement un nouveau nœud dom (souvent un enfant de document.body)

Vérifier la bibliothèque React-Slot-Fill


Bus d'événement

Comme indiqué dans la documentation React :

Pour la communication entre deux composants qui n'ont pas de relation parent-enfant, vous pouvez configurer votre propre système d'événements global. Abonnez-vous aux événements dans componentDidMount (), désabonnez-vous dans componentWillUnmount () et appelez setState () lorsque vous recevez un événement.

Il existe de nombreuses choses que vous pouvez utiliser pour configurer un bus d'événements. Vous pouvez simplement créer un tableau d'auditeurs, et lors de la publication de l'événement, tous les écouteurs recevraient l'événement. Ou vous pouvez utiliser quelque chose comme EventEmitter ou PostalJs


Flux

Flux est essentiellement un bus d'événements, sauf que les récepteurs d'événements sont des magasins. Ceci est similaire au système de bus d'événements de base, sauf que l'état est géré en dehors de React

L'implémentation de Flux d'origine ressemble à une tentative de faire du sourcing d'événements de manière hacky.

Redux est pour moi l'implémentation de Flux qui est la plus proche de l'événementiel, et bénéficie de nombreux avantages de l'événementiel comme la possibilité de voyager dans le temps. Il n'est pas strictement lié à React et peut également être utilisé avec d'autres bibliothèques de vues fonctionnelles.

Le tutoriel vidéo Redux d'Egghead est vraiment sympa et explique comment cela fonctionne en interne (c'est vraiment simple).


Curseurs

Les curseurs proviennent de ClojureScript / Om et sont largement utilisés dans les projets React. Ils permettent de gérer l'état en dehors de React et permettent à plusieurs composants d'accéder en lecture / écriture à la même partie de l'état, sans avoir besoin de connaître quoi que ce soit sur l'arborescence des composants.

De nombreuses implémentations existent, y compris ImmutableJS , React-cursors et Omniscient

Edit 2016 : il semble que les gens conviennent que les curseurs fonctionnent bien pour les petites applications, mais il ne s'adapte pas bien aux applications complexes. Om Next n'a plus de curseurs (alors que c'est Om qui a introduit le concept initialement)


Architecture d'orme

L' architecture Elm est une architecture proposée pour être utilisée par le langage Elm . Même si Elm n'est pas ReactJS, l'architecture Elm peut également être réalisée dans React.

Dan Abramov, l'auteur de Redux, a réalisé une implémentation de l'architecture Elm en utilisant React.

Redux et Elm sont vraiment excellents et ont tendance à renforcer les concepts de sourcing d'événements sur le frontend, permettant à la fois le débogage dans le temps, l'annulation / la restauration, la relecture ...

La principale différence entre Redux et Elm est que Elm a tendance à être beaucoup plus strict sur la gestion de l'État. Dans Elm, vous ne pouvez pas avoir d'état de composant local ou de montage / démontage de crochets et toutes les modifications DOM doivent être déclenchées par des changements d'état globaux. L'architecture Elm propose une approche évolutive qui permet de gérer TOUS les états à l'intérieur d'un seul objet immuable, tandis que Redux propose une approche qui vous invite à gérer la PLUPART des états dans un seul objet immuable.

Alors que le modèle conceptuel d'Elm est très élégant et que l'architecture permet de bien évoluer sur de grandes applications, il peut en pratique être difficile ou impliquer plus de passe-partout pour réaliser des tâches simples comme donner le focus à une entrée après l'avoir montée, ou l'intégrer à une bibliothèque existante avec une interface impérative (ie plugin JQuery). Problème connexe .

De plus, l'architecture Elm implique davantage de code passe-partout. Ce n'est pas verbeux ou compliqué à écrire, mais je pense que l'architecture Elm est plus adaptée aux langages typés statiquement.


FRP

Des bibliothèques comme RxJS, BaconJS ou Kefir peuvent être utilisées pour produire des flux FRP pour gérer la communication entre les composants.

Vous pouvez essayer par exemple Rx-React

Je pense que l'utilisation de ces bibliothèques est assez similaire à l'utilisation de ce que le langage ELM offre avec des signaux .

Le framework CycleJS n'utilise pas ReactJS mais utilise vdom . Il partage beaucoup de similitudes avec l'architecture Elm (mais est plus facile à utiliser dans la vie réelle car il autorise les hooks vdom) et il utilise largement les RxJ au lieu des fonctions, et peut être une bonne source d'inspiration si vous souhaitez utiliser FRP avec Réagir. Les vidéos CycleJs Egghead sont agréables à comprendre comment cela fonctionne.


CSP

Les CSP (Communicating Sequential Processes) sont actuellement populaires (principalement à cause de Go / goroutines et core.async / ClojureScript) mais vous pouvez également les utiliser en javascript avec JS-CSP .

James Long a réalisé une vidéo expliquant comment il peut être utilisé avec React.

Sagas

Une saga est un concept de backend issu du monde DDD / EventSourcing / CQRS, également appelé "gestionnaire de processus". Il est popularisé par le projet redux-saga , principalement en remplacement de redux-thunk pour la gestion des effets secondaires (c'est-à-dire les appels API, etc.). La plupart des gens pensent actuellement qu'il ne sert que pour les effets secondaires, mais qu'il s'agit en fait davantage de découplage des composants.

C'est plus un complément à une architecture Flux (ou Redux) qu'un système de communication totalement nouveau, car la saga émet des actions Flux à la fin. L'idée est que si vous avez widget1 et widget2 et que vous souhaitez les découpler, vous ne pouvez pas déclencher d'action ciblant widget2 depuis widget1. Donc, vous ne faites que widget1 déclencher des actions qui se ciblent, et la saga est un "processus d'arrière-plan" qui écoute les actions widget1, et peut envoyer des actions qui ciblent widget2. La saga est le point de couplage entre les 2 widgets mais les widgets restent découplés.

Si vous êtes intéressé, regardez ma réponse ici


Conclusion

Si vous souhaitez voir un exemple de la même petite application utilisant ces différents styles, vérifiez les branches de ce référentiel .

Je ne sais pas quelle est la meilleure option à long terme mais j'aime vraiment à quoi Flux ressemble à un événementiel.

Si vous ne connaissez pas les concepts de l'événementiel, jetez un œil à ce blog très pédagogique: tourner la base de données à l'envers avec apache Samza , c'est une lecture incontournable pour comprendre pourquoi Flux est sympa (mais cela pourrait aussi s'appliquer à FRP )

Je pense que la communauté convient que l'implémentation la plus prometteuse de Flux est Redux , qui permettra progressivement une expérience de développeur très productive grâce au rechargement à chaud. Le livecoding impressionnant de la vidéo Inventing on Principle de Bret Victor est possible!


2
Excellente réponse Seb!
Ali Gajani

7

OK, il y a peu de façons de le faire, mais je veux exclusivement me concentrer sur l'utilisation du magasin en utilisant Redux qui vous facilite la vie dans ces situations plutôt que de vous donner une solution rapide uniquement dans ce cas, l'utilisation de React pur finira par gâcher dans une très grande application et la communication entre les composants devient de plus en plus difficile à mesure que l'application se développe ...

Alors, que fait Redux pour vous?

Redux est comme le stockage local dans votre application qui peut être utilisé chaque fois que vous avez besoin de données à utiliser à différents endroits de votre application ...

Fondamentalement, l'idée de Redux provient du flux à l'origine, mais avec quelques changements fondamentaux, y compris le concept d'avoir une seule source de vérité en créant un seul magasin ...

Regardez le graphique ci-dessous pour voir quelques différences entre Flux et Redux ...

Redux et Flux

Envisagez d'appliquer Redux dans votre application dès le départ si votre application a besoin d'une communication entre les composants ...

La lecture de ces mots de la documentation Redux pourrait également être utile pour commencer:

Comme les exigences pour les applications JavaScript d'une seule page sont devenues de plus en plus complexes, notre code doit gérer plus d'états que jamais auparavant . Cet état peut inclure les réponses du serveur et les données mises en cache, ainsi que les données créées localement qui n'ont pas encore été conservées sur le serveur. L'état de l'interface utilisateur augmente également en complexité, car nous devons gérer les itinéraires actifs, les onglets sélectionnés, les filateurs, les contrôles de pagination, etc.

Gérer cet état en constante évolution est difficile. Si un modèle peut mettre à jour un autre modèle, une vue peut mettre à jour un modèle, ce qui met à jour un autre modèle, ce qui, à son tour, peut entraîner la mise à jour d'une autre vue. À un moment donné, vous ne comprenez plus ce qui se passe dans votre application, car vous avez perdu le contrôle de quand, pourquoi et comment son état. Lorsqu'un système est opaque et non déterministe, il est difficile de reproduire des bogues ou d'ajouter de nouvelles fonctionnalités.

Comme si cela ne suffisait pas, considérez que les nouvelles exigences deviennent courantes dans le développement de produits frontaux. En tant que développeurs, nous sommes censés gérer les mises à jour optimistes, le rendu côté serveur, la récupération des données avant d'effectuer des transitions de route, etc. Nous nous trouvons à essayer de gérer une complexité que nous n'avons jamais eu à affronter auparavant, et nous posons inévitablement la question: est-il temps d'abandonner? La réponse est non.

Cette complexité est difficile à gérer car nous mélangeons deux concepts très difficiles à raisonner pour l'esprit humain: la mutation et l'asynchronicité. Je les appelle Mentos et Coke. Les deux peuvent être parfaits en séparation, mais ensemble, ils créent un gâchis. Des bibliothèques comme React tentent de résoudre ce problème dans la couche de vue en supprimant à la fois l'asynchronie et la manipulation directe du DOM. Cependant, la gestion de l'état de vos données vous appartient. C'est là que Redux entre.

En suivant les étapes de Flux, CQRS et Event Sourcing , Redux tente de rendre les mutations d'état prévisibles en imposant certaines restrictions sur la manière et le moment où les mises à jour peuvent se produire . Ces restrictions se reflètent dans les trois principes de Redux.


Comment redux peut-il aider? si j'ai un modal pour un datepicker(en tant que composant) et que ce composant peut être chargé à partir de plusieurs composants vivant sur la même page, alors comment le datepickercomposant saura-t-il quelle action envoyer à redux? C'est l'essence du problème, reliant une action dans un composant à un autre et NON à aucun autre composant . (prenez en considération que le datepickerlui-même est un composant profond, profond au sein du composant modal lui-même)
vsync

@vsync ne pense pas que reudx est une valeur statique unique, redux est en fait peut avoir des objets, des tableaux ... vous pouvez donc les enregistrer en tant qu'objet ou tableau ou autre dans votre magasin, il peut s'agir de mapdispatchtoprops et chacun est enregistré dans un tableau d'objets comme: [{nom: "picker1", valeur: "01/01/1970"}, {nom: "picker2", valeur: "01/01/1980"}], puis utilisez mapstatetoprops dans le parent et transmettez-le à chacun des composants ou où vous voulez, je ne sais pas si cela répond à votre question, mais sans voir le code ... s'ils sont dans des groupes séparés, vous pouvez également vous opposer avec plus de détails ... mais tout dépend de la façon dont vous voulez grouper eux ..
Alireza

La question n'est pas de savoir reduxce que vous pouvez stocker, mais COMMENT transmettre l'action en profondeur à ce qui doit la déclencher. Comment un composant profond sait-il exactement ce qui doit être déclenché? car dans l'exemple, j'ai donné un composant général qui devrait se déclencher sur un réducteur spécifique, selon le scénario, il pourrait donc être un réducteur différent car un modal datepicker peut être utilisé pour n'importe quel composant.
vsync

5

C'est ainsi que j'ai géré cela.
Disons que vous avez un <select> pour le mois et un <select> pour le jour . Le nombre de jours dépend du mois sélectionné.

Les deux listes appartiennent à un troisième objet, le panneau de gauche. Les deux <select> sont également des enfants du leftPanel <div>
C'est un jeu avec les rappels et les gestionnaires dans le composant LeftPanel .

Pour le tester, copiez simplement le code dans deux fichiers séparés et exécutez index.html . Sélectionnez ensuite un mois et voyez comment le nombre de jours change.

dates.js

    /** @jsx React.DOM */


    var monthsLength = [0,31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
    var MONTHS_ARR = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];

    var DayNumber = React.createClass({
        render: function() {
            return (
                <option value={this.props.dayNum}>{this.props.dayNum}</option>
            );
        }
    });

    var DaysList = React.createClass({
        getInitialState: function() {
            return {numOfDays: 30};
        },
        handleMonthUpdate: function(newMonthix) {
            this.state.numOfDays = monthsLength[newMonthix];
            console.log("Setting days to " + monthsLength[newMonthix] + " month = " + newMonthix);

            this.forceUpdate();
        },
        handleDaySelection: function(evt) {
            this.props.dateHandler(evt.target.value);
        },
        componentDidMount: function() {
            this.props.readyCallback(this.handleMonthUpdate)
        },
        render: function() {
            var dayNodes = [];
            for (i = 1; i <= this.state.numOfDays; i++) {
                dayNodes = dayNodes.concat([<DayNumber dayNum={i} />]);
            }
            return (
                <select id={this.props.id} onChange = {this.handleDaySelection}>
                    <option value="" disabled defaultValue>Day</option>
                        {dayNodes}
                </select>
                );
        }
    });

    var Month = React.createClass({
        render: function() {
            return (
                <option value={this.props.monthIx}>{this.props.month}</option>
            );
        }
    });

    var MonthsList = React.createClass({
        handleUpdate: function(evt) {
            console.log("Local handler:" + this.props.id + " VAL= " + evt.target.value);
            this.props.dateHandler(evt.target.value);

            return false;
        },
        render: function() {
            var monthIx = 0;

            var monthNodes = this.props.data.map(function (month) {
                monthIx++;
                return (
                    <Month month={month} monthIx={monthIx} />
                    );
            });

            return (
                <select id = {this.props.id} onChange = {this.handleUpdate}>
                    <option value="" disabled defaultValue>Month</option>
                        {monthNodes}
                </select>
                );
        }
    });

    var LeftPanel = React.createClass({
        dayRefresh: function(newMonth) {
            // Nothing - will be replaced
        },
        daysReady: function(refreshCallback) {
            console.log("Regisering days list");
        this.dayRefresh = refreshCallback;
        },
        handleMonthChange: function(monthIx) {
            console.log("New month");
            this.dayRefresh(monthIx);
        },
        handleDayChange: function(dayIx) {
            console.log("New DAY: " + dayIx);
        },
        render: function() {
            return(
                <div id="orderDetails">
                    <DaysList id="dayPicker" dateHandler={this.handleDayChange} readyCallback = {this.daysReady} />
                    <MonthsList data={MONTHS_ARR} id="monthPicker" dateHandler={this.handleMonthChange}  />
                </div>
            );
        }
    });



    React.renderComponent(
        <LeftPanel />,
        document.getElementById('leftPanel')
    );

Et le code HTML pour exécuter le composant de panneau gauche index.html

<!DOCTYPE html>
<html>
<head>
    <title>Dates</title>

    <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.6.0/underscore-min.js"></script>
    <script src="//fb.me/react-0.11.1.js"></script>
    <script src="//fb.me/JSXTransformer-0.11.1.js"></script>
</head>

    <style>

        #dayPicker {
            position: relative;
            top: 97px;
            left: 20px;
            width: 60px;
            height: 17px;
        }

        #monthPicker {
            position: relative;
            top: 97px;
            left: 22px;
            width: 95px;
            height: 17px;
        }

        select {
            font-size: 11px;
        }

    </style>


    <body>
        <div id="leftPanel">
        </div>

        <script type="text/jsx" src="dates.js"></script>

    </body>
</html>

si vous pouviez supprimer 80% de l'exemple de code et garder votre point, ce serait mieux. montrant CSS dans le contexte de ce fil, il n'est pas pertinent
vsync

3

J'ai vu que la question est déjà répondue, mais si vous souhaitez en savoir plus, il existe au total 3 cas de communication entre composants :

  • Cas 1: Communication parent-enfant
  • Cas 2: communication de l'enfant au parent
  • Cas 3: Composants non liés (n'importe quel composant à n'importe quel composant) communication

1

En étendant la réponse de @MichaelLaCroix lorsqu'un scénario est que les composants ne peuvent pas communiquer entre toute sorte de relation parent-enfant, la documentation recommande de configurer un système d'événement global.

Dans le cas de <Filters />et <TopBar />n'ayant aucune des relations ci-dessus, un émetteur global simple pourrait être utilisé comme ceci:

componentDidMount - Abonnez-vous à l'événement

componentWillUnmount - Se désinscrire de l'événement

Code React.js et EventSystem

EventSystem.js

class EventSystem{

    constructor() {
        this.queue = {};
        this.maxNamespaceSize = 50;
    }

    publish(/** namespace **/ /** arguments **/) {
        if(arguments.length < 1) {
            throw "Invalid namespace to publish";
        }

        var namespace = arguments[0];
        var queue = this.queue[namespace];

        if (typeof queue === 'undefined' || queue.length < 1) {
            console.log('did not find queue for %s', namespace);
            return false;
        }

        var valueArgs = Array.prototype.slice.call(arguments);

        valueArgs.shift(); // remove namespace value from value args

        queue.forEach(function(callback) {
            callback.apply(null, valueArgs);
        });

        return true;
    }

    subscribe(/** namespace **/ /** callback **/) {
        const namespace = arguments[0];
        if(!namespace) throw "Invalid namespace";
        const callback = arguments[arguments.length - 1];
        if(typeof callback !== 'function') throw "Invalid callback method";

        if (typeof this.queue[namespace] === 'undefined') {
            this.queue[namespace] = [];
        }

        const queue = this.queue[namespace];
        if(queue.length === this.maxNamespaceSize) {
            console.warn('Shifting first element in queue: `%s` since it reached max namespace queue count : %d', namespace, this.maxNamespaceSize);
            queue.shift();
        }

        // Check if this callback already exists for this namespace
        for(var i = 0; i < queue.length; i++) {
            if(queue[i] === callback) {
                throw ("The exact same callback exists on this namespace: " + namespace);
            }
        }

        this.queue[namespace].push(callback);

        return [namespace, callback];
    }

    unsubscribe(/** array or topic, method **/) {
        let namespace;
        let callback;
        if(arguments.length === 1) {
            let arg = arguments[0];
            if(!arg || !Array.isArray(arg)) throw "Unsubscribe argument must be an array";
            namespace = arg[0];
            callback = arg[1];
        }
        else if(arguments.length === 2) {
            namespace = arguments[0];
            callback = arguments[1];
        }

        if(!namespace || typeof callback !== 'function') throw "Namespace must exist or callback must be a function";
        const queue = this.queue[namespace];
        if(queue) {
            for(var i = 0; i < queue.length; i++) {
                if(queue[i] === callback) {
                    queue.splice(i, 1); // only unique callbacks can be pushed to same namespace queue
                    return;
                }
            }
        }
    }

    setNamespaceSize(size) {
        if(!this.isNumber(size)) throw "Queue size must be a number";
        this.maxNamespaceSize = size;
        return true;
    }

    isNumber(n) {
        return !isNaN(parseFloat(n)) && isFinite(n);
    }

}

NotificationComponent.js

class NotificationComponent extends React.Component {

    getInitialState() {
        return {
            // optional. see alternative below
            subscriber: null
        };
    }

    errorHandler() {
        const topic = arguments[0];
        const label = arguments[1];
        console.log('Topic %s label %s', topic, label);
    }

    componentDidMount() {
        var subscriber = EventSystem.subscribe('error.http', this.errorHandler);
        this.state.subscriber = subscriber;
    }

    componentWillUnmount() {
        EventSystem.unsubscribe('error.http', this.errorHandler);

        // alternatively
        // EventSystem.unsubscribe(this.state.subscriber);
    }

    render() {

    }
}

0

Il y a une telle possibilité même si ce n'est pas une relation Parent - Enfant - et c'est Flux. Il y a une assez bonne implémentation (pour moi personnellement) pour celle appelée Alt.JS (avec Alt-Container).

Par exemple, vous pouvez avoir une barre latérale qui dépend de ce qui est défini dans les détails du composant. Le composant Sidebar est connecté à SidebarActions et SidebarStore, tandis que Details correspond à DetailsActions et DetailsStore.

Vous pouvez alors utiliser AltContainer comme ça

<AltContainer stores={{
                    SidebarStore: SidebarStore
                }}>
                    <Sidebar/>
</AltContainer>

{this.props.content}

Qui garderait les magasins (enfin je pourrais utiliser "store" au lieu de "stores" prop). Maintenant, {this.props.content} PEUT ÊTRE Détails selon l'itinéraire. Disons que / Details nous redirige vers cette vue. Les détails auraient par exemple une case à cocher qui changerait l'élément de la barre latérale de X à Y s'il était coché.

Techniquement, il n'y a pas de relation entre eux et il serait difficile de se passer de flux. MAIS AVEC CELA, c'est plutôt facile.

Passons maintenant à DetailsActions. Nous allons y créer

class SiteActions {
constructor() {
    this.generateActions(
        'setSiteComponentStore'
    );
}

setSiteComponent(value) {
    this.dispatch({value: value});
}
}

et DétailsStore

class SiteStore {
constructor() {
    this.siteComponents = {
        Prop: true
    };

    this.bindListeners({
        setSiteComponent: SidebarActions.COMPONENT_STATUS_CHANGED
    })
}

setSiteComponent(data) {
    this.siteComponents.Prop = data.value;
}
}

Et maintenant, c'est l'endroit où la magie commence.

Comme vous pouvez le voir, il y a bindListener à SidebarActions.ComponentStatusChanged qui sera utilisé SI setSiteComponent sera utilisé.

maintenant dans SidebarActions

    componentStatusChanged(value){
    this.dispatch({value: value});
}

Nous avons une telle chose. Il enverra cet objet sur appel. Et il sera appelé si setSiteComponent en magasin sera utilisé (que vous pouvez utiliser dans le composant par exemple pendant onChange on Button ou autre)

Maintenant, dans SidebarStore, nous aurons

    constructor() {
    this.structures = [];

    this.bindListeners({
        componentStatusChanged: SidebarActions.COMPONENT_STATUS_CHANGED
    })
}

    componentStatusChanged(data) {
    this.waitFor(DetailsStore);

    _.findWhere(this.structures[0].elem, {title: 'Example'}).enabled = data.value;
}

Maintenant, ici, vous pouvez voir qu'il attendra DetailsStore. Qu'est-ce que ça veut dire? plus ou moins, cela signifie que cette méthode doit attendre la mise à jour de DetailsStoreto avant de pouvoir se mettre à jour.

tl; dr One Store écoute les méthodes d'un magasin et déclenchera une action à partir de l'action du composant, qui mettra à jour son propre magasin.

J'espère que cela peut vous aider d'une manière ou d'une autre.


0

Si vous souhaitez explorer les options de communication entre les composants et avoir l'impression que cela devient de plus en plus difficile, vous pouvez envisager d'adopter un bon modèle de conception: Flux .

Il s'agit simplement d'une collection de règles qui définit la façon dont vous stockez et modifiez l'état à l'échelle de l'application et utilisez cet état pour rendre les composants.

Il existe de nombreuses implémentations de Flux, et l'implémentation officielle de Facebook en fait partie. Bien qu'il soit considéré comme celui qui contient le plus de code passe-partout, il est plus facile à comprendre car la plupart des choses sont explicites.

Certaines des autres alternatives sont flummox fluxxor fluxible et redux .


0

Une fois, j'étais là où vous êtes en ce moment, en tant que débutant, vous vous sentez parfois hors de propos sur la manière de réagir. Je vais essayer de m'attaquer de la même manière que j'y pense en ce moment.

Les États sont la pierre angulaire de la communication

Habituellement, cela se résume à la façon dont vous modifiez les états de ce composant dans votre cas, vous indiquez trois composants.

<List />: Qui affichera probablement une liste d'éléments en fonction d'un filtre <Filters />: Options de filtrage qui modifieront vos données. <TopBar />: Liste d'options.

Pour orchestrer toute cette interaction, vous allez avoir besoin d'un composant plus élevé appelons-le App, qui transmettra des actions et des données à chacun de ces composants afin par exemple de ressembler à ceci

<div>
  <List items={this.state.filteredItems}/>
  <Filter filter={this.state.filter} setFilter={setFilter}/>
</div>

Ainsi, quand setFilterest appelé, cela affectera le FilterItem et restituera les deux composants ;. Dans le cas où ce n'est pas tout à fait clair, je vous ai fait un exemple avec une case à cocher que vous pouvez archiver dans un seul fichier:

import React, {Component} from 'react';
import {render} from 'react-dom';

const Person  = ({person, setForDelete}) => (
          <div>
            <input type="checkbox" name="person" checked={person.checked} onChange={setForDelete.bind(this, person)} />
            {person.name}
          </div>
);


class PeopleList extends Component {

  render() {

    return(
      <div>
       {this.props.people.map((person, i) => {
         return <Person key={i} person={person} setForDelete={this.props.setForDelete} />;
       })}
       <div onClick={this.props.deleteRecords}>Delete Selected Records</div>
     </div>
    );
  }

} // end class

class App extends React.Component {

  constructor(props) {
    super(props)
    this.state = {people:[{id:1, name:'Cesar', checked:false},{id:2, name:'Jose', checked:false},{id:3, name:'Marbel', checked:false}]}
  }

  deleteRecords() {
    const people = this.state.people.filter(p => !p.checked);

    this.setState({people});
 }

  setForDelete(person) {
    const checked = !person.checked;
    const people = this.state.people.map((p)=>{
      if(p.id === person.id)
        return {name:person.name, checked};
      return p;
    });

    this.setState({people});
  }

  render () {

    return <PeopleList people={this.state.people} deleteRecords={this.deleteRecords.bind(this)} setForDelete={this.setForDelete.bind(this)}/>;
  }
}

render(<App/>, document.getElementById('app'));

0

Le code suivant m'aide à configurer la communication entre deux frères et sœurs. La configuration est effectuée dans leur parent lors des appels de render () et componentDidMount (). Il est basé sur https://reactjs.org/docs/refs-and-the-dom.html J'espère que cela vous aidera.

class App extends React.Component<IAppProps, IAppState> {
    private _navigationPanel: NavigationPanel;
    private _mapPanel: MapPanel;

    constructor() {
        super();
        this.state = {};
    }

    // `componentDidMount()` is called by ReactJS after `render()`
    componentDidMount() {
        // Pass _mapPanel to _navigationPanel
        // It will allow _navigationPanel to call _mapPanel directly
        this._navigationPanel.setMapPanel(this._mapPanel);
    }

    render() {
        return (
            <div id="appDiv" style={divStyle}>
                // `ref=` helps to get reference to a child during rendering
                <NavigationPanel ref={(child) => { this._navigationPanel = child; }} />
                <MapPanel ref={(child) => { this._mapPanel = child; }} />
            </div>
        );
    }
}

Il s'agit de TypeScript, devrait probablement être mentionné dans votre réponse. Bon concept cependant.
serraosays

0

Curieusement, personne n'a mentionné mobx. L'idée est similaire à redux. Si j'ai une donnée à laquelle plusieurs composants sont abonnés, je peux utiliser ces données pour piloter plusieurs composants.

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.