web-dev-qa-db-fra.com

ReactJS Deux composants communicants

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

Mon application est essentiellement une liste de filtres et un bouton permettant de modifier la mise en page . Pour le moment, j'utilise trois composants: <list />, < Filters /> et <TopBar />, maintenant évidemment lorsque je modifie les paramètres dans < Filters />, je souhaite déclencher une méthode <list /> pour mettre à jour ma vue.

Comment puis-je faire en sorte que ces 3 composants interagissent ou ai-je besoin d'un modèle de données global sur lequel je peux apporter des modifications?

271
woutr_be

La meilleure approche dépend de la manière dont vous envisagez d’organiser ces composants. Quelques exemples de scénarios qui me viennent à l’esprit en ce moment:

  1. <Filters /> est un composant enfant de <List />
  2. <Filters /> et <List /> sont des enfants d'un composant parent
  3. <Filters /> et <List /> résident entièrement dans des composants racine distincts.

Il y a peut-être d'autres scénarios auxquels je ne pense pas. Si le vôtre ne correspond pas à ces critères, faites-le-moi savoir. Voici quelques exemples très approximatifs de la manière dont j'ai géré les deux premiers scénarios:

Scénario 1

Vous pouvez passer un gestionnaire de <List /> à <Filters />, qui peut ensuite être appelé dans l'événement onChange 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 n ° 2

Semblable 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 n ° 3

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

299
Michael LaCroix

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 parent/enfant

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 callbacks/états (également appelés container ou smart component), et que tous les enfants soient sans état et ne rapportent que des éléments au parent. De cette façon, vous pouvez facilement "partager" l'état du parent avec tous les enfants qui en ont besoin.


Le contexte

Le contexte de réaction permet de conserver l’état à la racine de la hiérarchie de vos composants et d’injecter facilement cet état dans des composants imbriqués très profonds, sans avoir à passer par des tracas pour 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 motif render prop/children function

Cochez cette// blog post -/pour plus de détails.

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


Les portails

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

Dans ce cas, vous pouvez utiliser un "portail". Il existe différentes bibliothèques de réactions utilisant portals , généralement utilisées pour modals , des popups, des 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 dans 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 le remplissez d'un autre endroit de votre arbre 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>

Cela ressemble un peu aux portails, sauf que le contenu rempli sera rendu dans un emplacement que vous définissez, alors que les portails génèrent généralement un nouveau nœud dom (souvent un enfant de document.body).

Vérifiez react-slot-fill library


Bus de l'événement

Comme indiqué dans la documentation React -:

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

Il y a beaucoup de choses que vous pouvez utiliser pour configurer un bus d'événement. Vous pouvez simplement créer un ensemble d'écouteurs et, lors de la publication d'un événement, tous les écouteurs recevraient l'événement. Ou vous pouvez utiliser quelque chose comme/- EventEmitter ou PostalJs


Flux

Flux est fondamentalement un bus d'événement, sauf que les récepteurs d'événement 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 originale de Flux ressemble à une tentative de faire de l’évènement d’événement de manière simpliste.

Redux est pour moi l’implémentation de Flux qui est la plus proche de l’approvisionnement en événements, et qui bénéficie de nombreux avantages en termes d’approvisionnement en événements, comme la capacité 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.

Egghead Redux tutoriel vidéo est vraiment gentil et explique comment cela fonctionne en interne (c’est très simple).


Les 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'avoir un accès en lecture/écriture à la même partie de l'état, sans avoir besoin de rien savoir de l'arborescence des composants.

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

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


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 avec React.

Redux et Elm sont vraiment formidables et ont tendance à renforcer les concepts d’approvisionnement en événements sur le front-end, permettant le débogage dans le temps, l’annulation/la restauration, la relecture, etc.

La principale différence entre Redux et Elm est qu’Elm a tendance à être beaucoup plus strict en matière de gestion d’État. Dans Elm, vous ne pouvez pas avoir d’état de composant local ni de points d'ancrage montage/démontage et toutes les modifications du DOM doivent être déclenchées par des modifications d'état global. L'architecture Elm propose une approche évolutive permettant de gérer TOUT l'état à l'intérieur d'un seul objet immuable, tandis que Redux propose une approche vous invitant à gérer LA PLUPART de l'état dans un seul objet immuable .Bien que le modèle conceptuel d’Elm soit très élégant et que l’architecture permette de s’adapter facilement aux applications volumineuses, il peut être en pratique difficile ou plus compliqué d’accomplir des tâches simples, comme mettre l’accent sur une entrée après son montage ou l’intégration à une bibliothèque existante avec une interface impérative (ie plugin JQuery). Question connexe .

En outre, l'architecture Elm implique plus de code passe-partout. Ce n'est pas si compliqué à écrire mais je pense que l'architecture Elm est plus adaptée aux langages à typage statique.

.


FRP

Des bibliothèques telles que RxJS, BaconJS ou Kefir peuvent être utilisées pour produire des flux FRP afin de 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 propose avec signaux .

CycleJS framework n'utilise pas ReactJS mais utilise vdom . Il partage beaucoup de similitudes avec l'architecture Elm (mais il est plus facile à utiliser dans la vie réelle car il permet l'utilisation de vdom hooks). Il utilise beaucoup les RxJ au lieu de fonctions et peut constituer une bonne source d'inspiration si vous souhaitez utiliser FRP avec. Réagir. Vidéos CycleJs Egghead sont sympas pour comprendre comment cela fonctionne.

.


CSP

Les processus de communication séquentielle (CSP) sont actuellement populaires (principalement à cause de Go/goroutines et core.async/ClojureScript), mais vous pouvez les utiliser également en javascript avec JS-CSP .

James Long a réalisé une vidéo expliquant comment l’utiliser avec React.

Sagas.

Une saga est un concept 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 (appels API, etc.). La plupart des gens pensent qu’il ne s’agit que de services pour les effets secondaires, mais il s’agit plutôt de dissocier des composants.

C'est plus un complément à une architecture Flux (ou Redux) qu'un tout nouveau système de communication, 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 une action ciblant widget2 depuis widget1. Ainsi, vous ne faites en sorte que widget1 ne déclenche que des actions qui se ciblent, et la saga est un "processus en arrière-plan" qui écoute les actions de 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é, jetez un oeil à ma réponse ici .


Conclusion

Si vous voulez 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 beaucoup l'aspect de Flux dans la recherche d'événements. 

Si vous ne connaissez pas les concepts de sourcing d’événements, jetez un coup d’œil à ce blog très pédagogique: Inverser la base de données avec Apache Samza , c’est une lecture indispensable pour comprendre pourquoi Flux est Nice (mais cela pourrait s’appliquer à FRP aussi) 

Je pense que la communauté convient que l'implémentation de Flux la plus prometteuse est Redux , qui permettra progressivement une expérience de développement très productive aux développeurs grâce au rechargement à chaud. Un code de musique impressionnant tel que celui de Bret Victor La vidéo Inventer sur le principe est possible!

I think the community agrees that the most promising Flux implementation is Redux , which will progressively allow very productive developer experience thanks to hot reloading. Impressive livecoding ala Bret Victor's Inventing on Principle video is possible!

141
Sebastien Lorber

C'est la façon dont j'ai géré cela.
Disons que vous avez un <select> pour mois et un <select> pour 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 aussi des enfants du leftPanel <div>
C'est un jeu avec les callbacks et les gestionnaires dans le composant LeftPanel.

Pour le tester, copiez simplement le code dans deux fichiers séparés et exécutez le fichier index.html. Ensuite, sélectionnez 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 l'exécution du composant du panneau de 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>
5
Skulas

OK, il y a peu de façons de le faire, mais je veux uniquement me concentrer sur l'utilisation de store en utilisant Redux, ce qui vous simplifie la vie plutôt que de vous donner une solution rapide uniquement dans ce cas, en utilisant Pure React finira par être gâché par de grosses applications et la communication entre Components devient de plus en plus difficile à mesure que l'application grandit ...

Alors qu'est-ce que Redux fait pour vous?

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

Fondamentalement, l’idée de Redux vient de flux à l’origine, mais avec quelques changements fondamentaux, notamment le concept de disposer d’une 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 and Flux

Pensez à appliquer Redux à votre application dès le début si celle-ci nécessite une communication entre les composants ...

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

Comme les exigences pour les applications JavaScript à une seule page ont deviennent de plus en plus compliqués, notre code doit gérer plus d’état que jamais avant. Cet état peut inclure les réponses du serveur et les données en cache, ainsi que des données créées localement qui n'ont pas encore été conservées dans le fichier serveur. L’état de l’interface augmente également en complexité, comme nous devons le faire gérer les itinéraires actifs, les onglets sélectionnés, les filateurs, les commandes de pagination, etc.

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

Comme si cela ne suffisait pas, considérez que les nouvelles exigences deviennent commun dans le développement de produits front-end. En tant que développeurs, nous sommes devrait gérer les mises à jour optimistes, le rendu côté serveur, la récupération données avant d'effectuer des transitions d'itinéraire, et ainsi de suite. Nous nous trouvons en essayant de gérer une complexité que nous n’avons jamais eu à traiter avant, et nous posons inévitablement la question: est-il temps d'abandonner? Le la réponse est non.

Cette complexité est difficile à gérer car nous mélangeons deux concepts qui sont très difficiles pour l'esprit humain à raisonner sur: mutation et asynchronicité. Je les appelle Mentos et Coca-Cola. Les deux peuvent être grands en séparation, mais ensemble, ils créent un désordre. Des bibliothèques comme React essayez de résoudre ce problème dans la couche de vue en supprimant les deux asynchronisme et manipulation directe du DOM. Cependant, gérer l’état de vos données sont laissées à vous. C'est ici que Redux entre.

En suivant les étapes de Flux, CQRS et Event Sourcing, Redux tente de rendre prévisibles les mutations d’état en imposant certaines restrictions sur comment et quand les mises à jour peuvent arriver. Ces restrictions sont reflétés dans les trois principes de Redux.

4
Alireza

J'ai vu que la question est déjà posée, mais si vous souhaitez en savoir plus, il y a un total de 3 cas de communication entre composants :

  • Cas 1: communication parent-enfant
  • Cas 2: communication enfant à parent
  • Cas 3: communication entre composants non liés (composant à composant)
3
Kaloyan Kosev

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

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

componentDidMount - S'abonner à l'événement

componentWillUnmount - Se désabonner 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() {

    }
}
1
tsuz

Le code suivant m'aide à configurer la communication entre deux frères et soeurs. La configuration est effectuée dans leur parent lors des appels render () et composantDidMount () . Elle est basée sur https://reactjs.org/docs/refs-and-the-dom.html J'espère que ça aide.

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>
        );
    }
}
0
Sergei Zinovyev

Bizarrement, personne n'a mentionné mobx. L'idée est similaire à redux. Si plusieurs éléments sont abonnés à un élément de données, je peux utiliser ces données pour piloter plusieurs composants. 

0
windmaomao

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'un ensemble de règles qui définissent la manière dont vous stockez et modifiez l'état de l'application, et utilisez cet état pour restituer les composants.

Il existe de nombreuses implémentations de Flux et l'implémentation officielle de Facebook est l'une d'entre elles. Bien qu'il soit considéré comme celui qui contient le plus de code standard, il est cependant plus facile à comprendre car la plupart des choses sont explicites.

Certaines des autres alternatives sont flummoxfluxxorfluxible et redux .

0
Kemal Dağ

J'étais une fois où vous êtes en ce moment, en tant que débutant, vous ne vous sentez parfois pas à l'aise avec la manière de réagir. Je vais essayer de m'attaquer de la même manière que j'y pense maintenant. 

Les États sont la pierre angulaire de la communication

Habituellement, la manière dont vous modifiez les états de cette composante vous indique trois composantes.

<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 aurez besoin d'un composant plus élevé, appelons-le App, qui transmettra les actions et les 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>

Donc, quand setFilter est appelé, cela affectera le filterItem et rendra les deux composants ;. Au cas où cela ne serait pas tout à fait clair, je vous ai fourni un exemple avec une case à cocher dans laquelle vous pouvez archiver 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
cabolanoz

Il existe une telle possibilité même s’ils ne sont pas en relation parent-enfant - et c’est Flux. Il y a de très bonnes implémentations (pour moi personnellement) pour cela appelé Alt.JS (avec Alt-Container).

Par exemple, la barre latérale peut dépendre de ce qui est défini dans les détails du composant. Le composant Sidebar est connecté à SidebarActions et SidebarStore, tandis que Détails à DetailsActions et DetailsStore.

Vous pouvez utiliser alors AltContainer comme ça

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

{this.props.content}

Ce qui garderait des magasins (bien je pourrais utiliser "magasin" au lieu de "magasins" prop). Maintenant, {this.props.content} PEUT ÊTRE Détails en fonction de 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 Sidebar 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 créer là

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

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

et DetailsStore

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 in store sera utilisé (que vous pourrez utiliser dans composant par exemple lors de 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, 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 elle-même.

tl; dr Un magasin écoute les méthodes d'un magasin et déclenche 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
Shiroo