ReactJS deux composants communicants

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 composants: <list /> , < Filters /> et <TopBar /> , maintenant évidemment quand je change les paramètres dans < Filters /> je veux déclencher une méthode dans <list /> pour mettre à jour ma vue.

Comment faire pour que ces 3 composants interagissent avec chacun autre, ou Ai-je besoin d'une sorte de modèle de données global où je peux juste apporter des modifications?

258
demandé sur Dave Newton 2014-01-22 18:40:58

11 réponses

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

  1. <Filters /> est un composant enfant de <List />
  2. les deux <Filters /> et <List /> sont des enfants d'un parent
  3. <Filters /> et <List /> vivent entièrement dans des composants racinaires 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 n ° 1

vous pouvez passer un gestionnaire de <List /> à <Filters /> , qui peut alors être appelé sur l'événement onChange pour filtrer la liste avec la valeur courante.

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 #1, mais le composant parent sera celui qui passera la fonction handler à <Filters /> , et passera la liste filtrée à <List /> . Je préfère cette méthode puisqu'elle découple 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 n'importe quelle sorte de relation parent-enfant, la documentation recommande la mise en place d'un système d'événements global .

287
répondu Michael LaCroix 2014-01-24 00:54:29

il y a plusieurs façons de faire communiquer les composants. Certains peuvent être adaptés à votre cas d'utilisation. Voici une liste de quelques-uns que j'ai trouvé utiles 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, la composante enfant appellera un rappel fourni par le parent avec une valeur, et le parent sera en mesure d'obtenir la valeur fournie par les enfants dans le parent.

si vous construisez une fonctionnalité / page de votre application, il est préférable d'avoir un parent unique Gérer les callbacks/état (également appelé container ou smart component ), et tous les enfants d'être apatrides, ne rapportant les choses au parent. De cette façon, vous pouvez facilement "partager" l'état du parent avec n'importe quel enfant qui en a besoin.


Contexte

le contexte React permet de maintenir l'État à la racine de la hiérarchie des composants, et d'injecter ceci Etablissez facilement les composants très imbriqués, sans avoir à passer les accessoires à tous les composants intermédiaires.

Jusqu'à présent, le contexte était une caractéristique 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 render prop / children fonction pattern

Cochez cette blog post pour plus de détails.

avant React 16.3, je recommande d'utiliser react-broadcast qui offrent une API assez similaire, et d'utiliser L'ancienne API de contexte.


portails

utilisez un portail lorsque vous souhaitez garder 2 composants proches les uns des autres pour les faire communiquer avec des fonctions simples, comme dans parent / enfant normal, mais vous ne voulez pas que ces 2 composants aient une relation parent/enfant dans le DOM, en raison de / Les contraintes CSS qu'il implique (comme l'indice z, l'opacité...).

Dans ce cas, vous pouvez utiliser un "portail". Il existe différentes bibliothèques react en utilisant portails , généralement utilisé pour modals , popups, tooltips...

considérer ce qui suit:

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

pourrait produire la DOM suivante lorsqu'elle est rendue à l'intérieur de reactAppContainer :

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

plus les détails ici


"1519100920 de" Slots

vous définissez une fente quelque part, et puis vous remplissez la fente 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>

c'est un peu similaire aux portails sauf que le contenu rempli sera rendu dans un slot que vous définissez, alors que les portails rendent généralement un nouveau noeud dom (souvent un enfant de document.body)

Vérifier réagissent-fente de remplissage bibliothèque


"1519100920 de l'Événement" bus

comme indiqué dans la réaction documentation :

pour la communication entre deux composants qui n'ont pas de relation parent-enfant, vous pouvez configurer votre propre système d'événement global. Abonnez-vous à des événements dans componentDidMount(), désabonnez-vous dans componentWillUnmount(), 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énements. Vous pouvez simplement créer un tableau d'auditeurs, et sur l'événement publier, tous les auditeurs 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. C'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 du Flux ressemble à une tentative de faire de l'Event-sourcing d'une manière hacky.

Redux est pour moi L'implémentation de Flux qui est la plus proche de l'event-sourcing, un avantage de nombreux avantages de l'event-sourcing comme la possibilité de voyager dans le temps. Il n'est pas strictement lié à React et peut également être utilisé avec d'autres vues fonctionnelles bibliothèque.

Egghead's Redux tutoriel vidéo est vraiment agréable et explique comment cela fonctionne à l'interne (il est vraiment simple).


Curseurs

Curseurs proviennent de ClojureScript / Om et largement utilisé dans les projets React. Ils permettent de gérer l'état en dehors de Réagir, et permettent à plusieurs composants ont accès en lecture/écriture à la même partie de la état, sans avoir besoin de savoir quoi que ce soit sur l'arbre des composants.

plusieurs implémentations existent, y compris immutable js , React-cursors et Omniscient "1519280920

Edit 2016 : il semble que les gens s'accordent à dire que les curseurs fonctionnent bien pour les applications plus petites, mais qu'ils ne se dimensionnent pas bien sur les applications complexes. Om Next n'a plus de curseurs (alors que C'est Om que introduction initiale du concept)


Elm architecture

le Elm architecture est une architecture proposée Pour être utilisée par le Elm language . Même si Elm n'est pas ReactJS, L'architecture Elm peut aussi être réalisée dans React.

Dan Abramov, l'auteur de Redux, a fait une mise en œuvre de l'architecture Elm en utilisant Réagir.

les deux Redux et Elm sont vraiment grands et ont tendance à renforcer les concepts d'event-sourcing sur le frontal, les deux permettant time-travel debugging, undo/redo, replay...

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

alors que le modèle conceptuel de L'Elm est très élégant et que l'architecture permet de bien se dimensionner sur les grandes applications, il peut en pratique être difficile ou impliquer plus de boilerplate pour réaliser des tâches simples comme donner la priorité à une entrée après le montage, ou l'intégration avec une bibliothèque existante avec une interface impérative (ex: jQuery plugin). Lié à la question de .

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


PRF

Les bibliothèques

comme RxJS, BaconJS ou Kefir peuvent être utilisées pour produire des flux de PRF pour gérer la communication entre les composants.

Vous pouvez essayer par exemple Rx-Réagir

je pense que l'utilisation de ces libs est tout à fait similaire à l'utilisation de ce que la langue ELM offre avec signaux .

CycleJS cadre n'utilise pas ReactJS mais utilise des vdom . Il partage beaucoup de similitudes avec l'architecture Elm (mais est plus facile à utiliser dans la vie réelle parce qu'il permet des crochets vdom) et il utilise RxJS largement au lieu de fonctions, et peut être une bonne source d'inspiration si vous voulez utiliser FRP avec React. CycleJs tête d'oeuf vidéos sont agréables à comprendre comment il fonctionne.


CSP

CSP (Communication de Processus Séquentiels) sont actuellement populaire (surtout à cause des Aller/goroutines et le noyau.async / ClojureScript) mais vous pouvez les utiliser aussi en javascript avec JS-CSP .

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

Sagas

une saga est un concept d'arrière-plan issu du monde DDD / EventSourcing / CQRS, également appelé"process manager". Il est popularisé par le projet redux-saga , principalement en remplacement de redux-thunk pour le traitement des effets secondaires (appels API, etc.). La plupart des gens pensent actuellement qu'il ne s'agit que de services pour les effets secondaires, mais il est en fait plus sur les composants de découplage.

c'est plus un complément à une architecture de Flux (ou Redux) qu'un système de communication totalement nouveau, parce que la saga émet des actions de Flux à la fin. L'idée est que si vous avez widget1 et widget2, et que vous voulez qu'ils soient découplés, vous ne pouvez pas lancer l'action ciblant widget2 à partir de widget1. Si vous make widget1 ne lance que des actions qui se ciblent, et la saga est un "processus de fond" qui écoute les actions widget1, et peut envoyer des actions qui ciblent widget 2. La saga est le couplage entre les 2 widgets mais les widgets restent découplés.

si vous êtes intéressé jeter un oeil à ma réponse ici


Conclusion

Si vous voulez voir un exemple de l' même petite application en utilisant ces différents styles, vérifiez les branches de ce dépôt .

Je ne sais pas quelle est la meilleure option à long terme, mais J'aime vraiment comment Flux ressemble à l'event-sourcing.

si vous ne connaissez pas les concepts de sourcing d'événements, jetez un coup d'oeil à ce blog très pédagogique: Turning the database inside out with apache Samza , c'est un must-read to understand why Flux is nice (but cela pourrait également s'appliquer au PRF)

je pense que la communauté convient que la mise en œuvre de Flux la plus prometteuse est Redux , qui permettra progressivement une expérience de développeur très productive grâce à un rechargement à chaud. Impressionnant livecoding Ala Bret Victor Inventing on Principle video est possible!

126
répondu Sebastien Lorber 2018-09-17 08:17:58

C'est comme ça que j'ai géré ça.

Disons que vous avez un pour Jour . Le nombre de jours dépend du mois choisi.

les deux listes appartiennent à un troisième objet, le panneau de gauche. Les deux