ReactJS Zwei Komponenten kommunizieren

321

Ich habe gerade mit ReactJS angefangen und bin ein wenig auf ein Problem fixiert, das ich habe.

Meine Anwendung ist im Wesentlichen eine Liste mit Filtern und einer Schaltfläche zum Ändern des Layouts. Im Moment bin ich mit drei Komponenten: <list />, < Filters />und <TopBar />, nun offensichtlich , wenn ich die Einstellungen ändern in < Filters />ich eine Methode zur Auslösung in wollen <list />aktualisieren meine Ansicht.

Wie kann ich diese drei Komponenten miteinander interagieren lassen oder benötige ich ein globales Datenmodell, an dem ich nur Änderungen vornehmen kann?

woutr_be
quelle
Sind alle drei Geschwisterkomponenten oder ist eine in der anderen?
pgreen2
Sie sind alle drei Komponenten. Ich habe meine Anwendung bereits neu angeordnet, sodass sie jetzt alle denselben Elternteil haben, der sie mit Daten versorgen kann.
woutr_be
4
Hier können Sie Flussmittel oder Pubsub-Muster verwenden. Basierend auf den Dokumenten in den Reaktionsdokumenten hinterlassen sie einen etwas mehrdeutigen Satz: "Für die Kommunikation zwischen zwei Komponenten, die keine Eltern-Kind-Beziehung haben, können Sie Ihr eigenes globales Ereignissystem einrichten." facebook.github.io/react/tips/…
BingeBoy
@BingeBoy ist richtig Flux ist eine großartige Möglichkeit, reaktionsfähige Apps zu schreiben, die das Problem des Datenflusses und der gemeinsamen Nutzung von Daten durch viele Komponenten lösen können.
Ankit Patial
4
Wenn Sie nicht in Flux oder Redux einsteigen möchten, ist dies ein großartiger Artikel zu diesem Thema andrewhfarmer.com/component-communication
garajo

Antworten:

318

Der beste Ansatz hängt davon ab, wie Sie diese Komponenten anordnen möchten. Einige Beispielszenarien, die mir gerade in den Sinn kommen:

  1. <Filters /> ist eine untergeordnete Komponente von <List />
  2. Beide <Filters />und <List />sind untergeordnete Elemente einer übergeordneten Komponente
  3. <Filters />und <List />leben in separaten Wurzelkomponenten vollständig.

Es kann andere Szenarien geben, an die ich nicht denke. Wenn deins nicht in diese passt, dann lass es mich wissen. Hier sind einige sehr grobe Beispiele dafür, wie ich mit den ersten beiden Szenarien umgegangen bin:

Szenario 1

Sie können einen Handler von <List />bis übergeben <Filters />, der dann für das onChangeEreignis aufgerufen werden kann , um die Liste mit dem aktuellen Wert zu filtern.

JSFiddle für # 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);

Szenario 2

Ähnlich wie in Szenario 1, jedoch wird die übergeordnete Komponente die Handlerfunktion an übergeben <Filters />und die gefilterte Liste an übergeben <List />. Ich mag diese Methode besser, da sie die <List />von der entkoppelt <Filters />.

JSFiddle für # 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);

Szenario 3

Wenn die Komponenten nicht zwischen Eltern-Kind-Beziehungen kommunizieren können, wird in der Dokumentation empfohlen, ein globales Ereignissystem einzurichten .

Michael LaCroix
quelle
6
Das Schöne an # 2 ist, dass sie sich nur auf einen Elternteil verlassen, der jeder Komponente eine Requisite übergibt: eine Funktion updateFilterin <Filters />Bezug auf und ein Array itemsin Bezug auf <List />. Sie können diese untergeordneten Komponenten in anderen Eltern mit unterschiedlichen Verhaltensweisen verwenden, entweder zusammen oder alleine. Zum Beispiel, wenn Sie eine dynamische Liste anzeigen möchten, aber keine Filterung benötigen.
Michael LaCroix
2
@woutr_be Wir sind uns nicht sicher, ob es zu Ihrer Anforderung passt, aber in einer ähnlichen Situation haben wir vor einiger Zeit die folgenden zwei Funktionen verwendet, um die Kommunikation zwischen der untergeordneten und der übergeordneten Komponente zu sortieren: - listenTo: function (eventName, eventCallback) {$ ( window.document) .bind (eventName, eventCallback);} triggerEvent: function (eventName, params) {$ .event.trigger (eventName, params);} Hoffe, es hilft! (Entschuldigung, könnte es nicht besser formatieren)
5122014009
29
Gibt es für Szenario 3 einen empfohlenen Ansatz? Gibt es Dokumente oder Beispiele dazu, indem Sie benutzerdefinierte synthetische Ereignisse generieren? Ich habe in den Hauptdokumenten nichts gefunden.
Pwray
1
Szenario 2 macht sehr viel Sinn ... bis Sie das Design gefährden müssen (wenn nur Layout) - dann erkennen Sie die Notwendigkeit eines EventHub / PubSub.
Cody
4
Der Link für Szenario 3 ist tot und leitet jetzt zu einer nicht verwandten React-Dokumentenseite weiter.
Beporter
170

Es gibt mehrere Möglichkeiten, Komponenten zur Kommunikation zu bringen. Einige können für Ihren Anwendungsfall geeignet sein. Hier ist eine Liste von einigen, die ich als nützlich erachtet habe.

Reagieren

Direkte Kommunikation zwischen Eltern und Kind

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}/>
    )
  }
}

Hier ruft die untergeordnete Komponente einen vom übergeordneten Element bereitgestellten Rückruf mit einem Wert auf, und das übergeordnete Element kann den von den untergeordneten Elementen im übergeordneten Element bereitgestellten Wert abrufen.

Wenn Sie eine Funktion / Seite Ihrer App erstellen, ist es besser, wenn ein einzelner Elternteil die Rückrufe / den Status (auch containeroder genannt smart component) verwaltet und alle Kinder zustandslos sind und nur dem Elternteil Bericht erstatten. Auf diese Weise können Sie den Status des Elternteils problemlos an jedes Kind weitergeben, das ihn benötigt.


Kontext

Mit React Context können Sie den Status an der Wurzel Ihrer Komponentenhierarchie halten und diesen Status problemlos in sehr tief verschachtelte Komponenten einfügen, ohne dass Sie Requisiten an alle Zwischenkomponenten weitergeben müssen.

Bisher war der Kontext eine experimentelle Funktion, aber in React 16.3 ist eine neue API verfügbar.

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>
);

Der Verbraucher nutzt die render prop / children

Überprüfen Sie diesen Blog-Beitrag für weitere Details.

Vor React 16.3 würde ich die Verwendung von React -Broadcast empfehlen, die eine ziemlich ähnliche API bieten, und die frühere Kontext-API verwenden.


Portale

Verwenden Sie ein Portal, wenn Sie zwei Komponenten nahe beieinander halten möchten, damit sie mit einfachen Funktionen kommunizieren können, wie dies bei normalen Eltern / Kindern der Fall ist. Sie möchten jedoch nicht, dass diese beiden Komponenten eine Eltern / Kind-Beziehung im DOM haben, da der damit verbundenen visuellen / CSS-Einschränkungen (wie Z-Index, Deckkraft ...).

In diesem Fall können Sie ein "Portal" verwenden. Es gibt verschiedene Reaktionsbibliotheken mit Portalen , die normalerweise für Modale , Popups, Tooltips ... verwendet werden.

Folgendes berücksichtigen:

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

Könnte beim Rendern im Inneren das folgende DOM erzeugen reactAppContainer:

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

Weitere Details hier


Schlüssel

Sie definieren irgendwo einen Slot und füllen den Slot dann an einer anderen Stelle Ihres Renderbaums.

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>

Dies ist Portalen etwas ähnlich, außer dass der gefüllte Inhalt in einem von Ihnen definierten Slot gerendert wird, während Portale im Allgemeinen einen neuen Dom-Knoten rendern (häufig untergeordnete Elemente von document.body).

Überprüfen Sie die React-Slot-Fill- Bibliothek


Ereignisbus

Wie in der React- Dokumentation angegeben :

Für die Kommunikation zwischen zwei Komponenten, die keine Eltern-Kind-Beziehung haben, können Sie Ihr eigenes globales Ereignissystem einrichten. Abonnieren Sie Ereignisse in componentDidMount (), melden Sie sich in componentWillUnmount () ab und rufen Sie setState () auf, wenn Sie ein Ereignis erhalten.

Es gibt viele Dinge, mit denen Sie einen Ereignisbus einrichten können. Sie können einfach ein Array von Listenern erstellen. Bei der Veröffentlichung eines Ereignisses erhalten alle Listener das Ereignis. Oder Sie können etwas wie EventEmitter oder PostalJs verwenden


Fluss

Flux ist im Grunde ein Ereignisbus, außer dass die Ereignisempfänger Geschäfte sind. Dies ähnelt dem grundlegenden Ereignisbussystem, außer dass der Status außerhalb von React verwaltet wird

Die ursprüngliche Flux-Implementierung scheint ein Versuch zu sein, Event-Sourcing auf hackige Weise durchzuführen.

Redux ist für mich die Flux-Implementierung, die der Event-Beschaffung am nächsten kommt. Sie bietet viele Vorteile der Event-Beschaffung, z. B. die Möglichkeit, Zeitreisen zu unternehmen. Es ist nicht eng mit React verknüpft und kann auch mit anderen funktionalen Ansichtsbibliotheken verwendet werden.

Das Redux- Video-Tutorial von Egghead ist wirklich nett und erklärt, wie es intern funktioniert (es ist wirklich einfach).


Cursor

Cursor kommen aus ClojureScript / Om und werden häufig in React-Projekten verwendet. Sie ermöglichen die Verwaltung des Status außerhalb von React und ermöglichen mehreren Komponenten Lese- / Schreibzugriff auf denselben Teil des Status, ohne dass Sie etwas über den Komponentenbaum wissen müssen.

Es gibt viele Implementierungen, einschließlich ImmutableJS , React-Cursors und Omniscient

Edit 2016 : Es scheint, dass die Leute der Meinung sind, dass Cursor für kleinere Apps gut funktionieren, aber für komplexe Apps nicht gut skalierbar sind. Om Next hat keine Cursor mehr (während Om das Konzept ursprünglich eingeführt hat)


Ulmenarchitektur

Die Elm-Architektur ist eine Architektur, die von der Elm-Sprache verwendet werden soll . Auch wenn Elm nicht ReactJS ist, kann die Elm-Architektur auch in React ausgeführt werden.

Dan Abramov, der Autor von Redux, hat eine Implementierung der Elm-Architektur mit React durchgeführt.

Sowohl Redux als auch Elm sind wirklich großartig und tendieren dazu, Event-Sourcing-Konzepte im Frontend zu unterstützen. Beide ermöglichen das Debuggen von Zeitreisen, das Rückgängigmachen / Wiederherstellen, die Wiedergabe ...

Der Hauptunterschied zwischen Redux und Elm besteht darin, dass Elm in Bezug auf das staatliche Management tendenziell viel strenger ist. In Elm können keine lokalen Komponentenstatus- oder Mount / Unmount-Hooks vorhanden sein, und alle DOM-Änderungen müssen durch globale Statusänderungen ausgelöst werden. Die Elm-Architektur schlägt einen skalierbaren Ansatz vor, mit dem der gesamte Status innerhalb eines einzelnen unveränderlichen Objekts verarbeitet werden kann, während Redux einen Ansatz vorschlägt, mit dem Sie aufgefordert werden, den größten Teil des Status in einem einzelnen unveränderlichen Objekt zu verarbeiten.

Während das konzeptionelle Modell von Elm sehr elegant ist und die Architektur eine gute Skalierung auf großen Apps ermöglicht, kann es in der Praxis schwierig sein oder mehr Boilerplate erfordern, um einfache Aufgaben wie das Fokussieren auf eine Eingabe nach dem Mounten oder das Integrieren in eine vorhandene Bibliothek zu erledigen mit einer zwingenden Schnittstelle (dh JQuery-Plugin). Verwandte Ausgabe .

Außerdem beinhaltet die Elm-Architektur mehr Code-Boilerplate. Es ist nicht so ausführlich oder kompliziert zu schreiben, aber ich denke, die Elm-Architektur eignet sich eher für statisch typisierte Sprachen.


FRP

Bibliotheken wie RxJS, BaconJS oder Kefir können verwendet werden, um FRP-Streams für die Kommunikation zwischen Komponenten zu erstellen.

Sie können zum Beispiel Rx-React ausprobieren

Ich denke, die Verwendung dieser Bibliotheken ähnelt der Verwendung der ELM-Sprache mit Signalen .

Das CycleJS- Framework verwendet nicht ReactJS, sondern vdom . Es hat viele Ähnlichkeiten mit der Elm-Architektur (ist aber im wirklichen Leben einfacher zu verwenden, da es Vdom-Hooks zulässt) und verwendet RxJs häufig anstelle von Funktionen. Es kann eine gute Inspirationsquelle sein, wenn Sie FRP verwenden möchten Reagieren. CycleJs Egghead-Videos sind gut zu verstehen, wie es funktioniert.


CSP

CSP (Communicating Sequential Processes) sind derzeit beliebt (hauptsächlich wegen Go / goroutines und core.async / ClojureScript), aber Sie können sie auch in Javascript mit JS-CSP verwenden .

James Long hat ein Video gemacht, in dem erklärt wird, wie es mit React verwendet werden kann.

Sagas

Eine Saga ist ein Backend-Konzept, das aus der DDD / EventSourcing / CQRS-Welt stammt und auch als "Prozessmanager" bezeichnet wird. Es wird vom Redux-Saga- Projekt populär gemacht , hauptsächlich als Ersatz für Redux-Thunk zur Behandlung von Nebenwirkungen (dh API-Aufrufe usw.). Die meisten Leute denken derzeit, dass es nur um Nebenwirkungen geht, aber es geht eigentlich mehr darum, Komponenten zu entkoppeln.

Es ist eher ein Kompliment an eine Flux-Architektur (oder Redux) als an ein völlig neues Kommunikationssystem, da die Saga am Ende Flux-Aktionen ausgibt. Die Idee ist, dass Sie, wenn Sie Widget1 und Widget2 haben und möchten, dass sie entkoppelt werden, keine Aktion auslösen können, die auf Widget2 von Widget1 abzielt. Sie lassen also Widget1 nur Aktionen abfeuern, die auf sich selbst abzielen, und die Saga ist ein "Hintergrundprozess", der auf Widget1-Aktionen wartet und möglicherweise Aktionen auslöst, die auf Widget2 abzielen. Die Saga ist der Kopplungspunkt zwischen den beiden Widgets, aber die Widgets bleiben entkoppelt.

Wenn Sie interessiert sind, schauen Sie sich meine Antwort hier an


Fazit

Wenn Sie ein Beispiel derselben kleinen App mit diesen verschiedenen Stilen sehen möchten, überprüfen Sie die Zweige dieses Repositorys .

Ich weiß nicht, was auf lange Sicht die beste Option ist, aber ich mag es wirklich, wie Flux wie Event-Sourcing aussieht.

Wenn Sie keine Event-Sourcing-Konzepte kennen, werfen Sie einen Blick auf diesen sehr pädagogischen Blog: Wenn Sie die Datenbank mit Apache Samza auf den Kopf stellen , ist es ein Muss zu verstehen, warum Flux nett ist (dies könnte jedoch auch für FRP gelten )

Ich denke, die Community ist sich einig, dass die vielversprechendste Flux-Implementierung Redux ist , die dank Hot-Reloading nach und nach eine sehr produktive Entwicklererfahrung ermöglicht. Beeindruckende Live-Codierung von Bret Victors Video " Inventing on Principle" ist möglich!

Sebastien Lorber
quelle
2
Hervorragende Antwort Seb!
Ali Gajani
7

OK, es gibt nur wenige Möglichkeiten, dies zu tun, aber ich möchte mich ausschließlich auf die Verwendung von Store mit Redux konzentrieren , was Ihnen das Leben in diesen Situationen erheblich erleichtert, anstatt Ihnen nur für diesen Fall eine schnelle Lösung zu bieten wirklich große Anwendung und Kommunikation zwischen Komponenten wird immer schwieriger, je größer die Anwendung wird ...

Also was Redux für Sie?

Redux ist wie ein lokaler Speicher in Ihrer Anwendung, der verwendet werden kann, wenn Daten an verschiedenen Stellen in Ihrer Anwendung verwendet werden müssen ...

Grundsätzlich stammt die Redux-Idee ursprünglich aus dem Fluss, aber mit einigen grundlegenden Änderungen, einschließlich des Konzepts, eine Quelle der Wahrheit zu haben, indem nur ein Geschäft erstellt wird ...

Schauen Sie sich die Grafik unten an, um einige Unterschiede zwischen Flux und Redux zu sehen ...

Redux und Flux

Ziehen Sie in Betracht, Redux anzuwenden in von Anfang an in Ihrer Anwendung wenn Ihre Anwendung eine Kommunikation zwischen Komponenten benötigt ...

Das Lesen dieser Wörter aus der Redux-Dokumentation kann zunächst hilfreich sein:

Da die Anforderungen für JavaScript-Anwendungen mit nur einer Seite immer komplizierter werden, muss unser Code mehr Status als je zuvor verwalten . Dieser Status kann Serverantworten und zwischengespeicherte Daten sowie lokal erstellte Daten enthalten, die noch nicht auf dem Server gespeichert wurden. Der UI-Status wird auch immer komplexer, da wir aktive Routen, ausgewählte Registerkarten, Spinner, Paginierungssteuerelemente usw. verwalten müssen.

Es ist schwierig, diesen sich ständig ändernden Zustand zu bewältigen. Wenn ein Modell ein anderes Modell aktualisieren kann, kann eine Ansicht ein Modell aktualisieren, wodurch ein anderes Modell aktualisiert wird. Dies kann wiederum dazu führen, dass eine andere Ansicht aktualisiert wird. Irgendwann verstehen Sie nicht mehr, was in Ihrer App passiert, da Sie die Kontrolle über das Wann, Warum und Wie ihres Status verloren haben. Wenn ein System undurchsichtig und nicht deterministisch ist, ist es schwierig, Fehler zu reproduzieren oder neue Funktionen hinzuzufügen.

Als ob dies nicht schlimm genug wäre, sollten Sie die neuen Anforderungen berücksichtigen, die in der Front-End-Produktentwicklung üblich werden. Als Entwickler wird von uns erwartet, dass wir optimistische Updates, serverseitiges Rendern, Abrufen von Daten vor dem Ausführen von Routenübergängen usw. durchführen. Wir versuchen, mit einer Komplexität umzugehen, mit der wir uns noch nie befasst haben, und stellen unweigerlich die Frage: Ist es Zeit aufzugeben? Die Antwort ist nein.

Diese Komplexität ist schwer zu bewältigen, da wir zwei Konzepte mischen, über die der menschliche Verstand nur schwer nachdenken kann: Mutation und Asynchronität. Ich nenne sie Mentos und Cola. Beide können in der Trennung großartig sein, aber zusammen verursachen sie ein Chaos. Bibliotheken wie React versuchen, dieses Problem in der Ansichtsebene zu lösen, indem sie sowohl Asynchronität als auch direkte DOM-Manipulation entfernen. Die Verwaltung des Status Ihrer Daten bleibt jedoch Ihnen überlassen. Hier kommt Redux ins Spiel.

In den Schritten von Flux, CQRS und Event Sourcing versucht Redux, Zustandsmutationen vorhersehbar zu machen, indem bestimmte Einschränkungen festgelegt werden, wie und wann Aktualisierungen erfolgen können . Diese Einschränkungen spiegeln sich in den drei Prinzipien von Redux wider.

Alireza
quelle
Wie kann Redux helfen? Wenn ich ein Modal für a datepicker(als Komponente) habe und diese Komponente von mehreren Komponenten auf derselben Seite geladen werden kann, woher datepickerweiß die Komponente dann, welche Aktion an redux gesendet werden soll? Dies ist die Essenz des Problems, eine Aktion in einer Komponente mit einer anderen und NICHT mit einer anderen Komponente zu verknüpfen . (Berücksichtigen Sie, dass das datepicker
Selbst
@vsync glaubt nicht, dass reudx ein einzelner statischer Wert ist. Redux kann tatsächlich Objekte, Arrays haben. Sie können sie also als Objekt oder Array oder was auch immer in Ihrem Geschäft speichern. Es können mapdispatchtoprops sein und jedes wird im Objektarray gespeichert wie: [{name: "picker1", value: "01/01/1970"}, {name: "picker2", value: "01/01/1980"}] und verwende dann mapstatetoprops im übergeordneten Element und übergebe es an Die einzelnen Komponenten oder wo immer Sie möchten, sind sich nicht sicher, ob sie Ihre Frage beantworten, aber ohne den Code zu sehen ... Wenn sie sich in separaten Gruppen befinden, können Sie auch mit weiteren Details Einwände erheben ... aber es hängt alles davon ab, wie Sie gruppieren möchten sie ..
Alireza
Die Frage ist nicht über , reduxund was Sie speichern können, aber WIE die Aktion tief passieren , was es braucht auszulösen. Woher weiß eine tiefe Komponente, was GENAU ausgelöst werden muss? Da ich im Beispiel eine allgemeine Komponente angegeben habe, die je nach Szenario einen bestimmten Reduzierer auslösen sollte, kann es sich also um einen anderen Reduzierer handeln, da für jede Komponente ein Datepicker- Modal verwendet werden kann.
vsync
5

So habe ich damit umgegangen.
Angenommen, Sie haben eine <Auswahl> für Monat und eine <Auswahl> für Tag . Die Anzahl der Tage hängt vom ausgewählten Monat ab.

Beide Listen gehören einem dritten Objekt, dem linken Bereich. Beide <select> sind auch untergeordnete Elemente des leftPanel <div>.
Es ist ein Spiel mit den Rückrufen und den Handlern in der LeftPanel- Komponente.

Um es zu testen, kopieren Sie einfach den Code in zwei getrennte Dateien und führen Sie die index.html aus . Wählen Sie dann einen Monat aus und sehen Sie, wie sich die Anzahl der Tage ändert.

Termine.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')
    );

Und der HTML- Code zum Ausführen der linken Indexkomponente 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>
Skulas
quelle
Wenn Sie 80% des Beispielcodes löschen und trotzdem Ihren Standpunkt beibehalten könnten, wäre dies am besten. CSS im Kontext dieses Threads zu zeigen ist irrelevant
vsync
3

Ich habe gesehen, dass die Frage bereits beantwortet ist, aber wenn Sie mehr Details erfahren möchten, gibt es insgesamt 3 Fälle von Kommunikation zwischen Komponenten :

  • Fall 1: Eltern-Kind-Kommunikation
  • Fall 2: Kommunikation zwischen Kind und Eltern
  • Fall 3: Kommunikation nicht verwandter Komponenten (jede Komponente zu jeder Komponente)
Kaloyan Kosev
quelle
1

In der Dokumentation wird empfohlen, ein globales Ereignissystem einzurichten, um die Antwort von @MichaelLaCroix zu erweitern, wenn in einem Szenario die Komponenten nicht zwischen Eltern-Kind-Beziehungen kommunizieren können.

Im Fall von <Filters />und <TopBar />ohne eine der oben genannten Beziehungen könnte ein einfacher globaler Emitter wie folgt verwendet werden:

componentDidMount - Abonnieren Sie die Veranstaltung

componentWillUnmount - Abmeldung vom Event

React.js und EventSystem-Code

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() {

    }
}
tsuz
quelle
0

Es gibt eine solche Möglichkeit, auch wenn es sich nicht um eine Eltern-Kind-Beziehung handelt - und das ist Flux. Es gibt eine ziemlich gute (für mich persönlich) Implementierung für Alt.JS (mit Alt-Container).

Beispielsweise können Sie eine Seitenleiste haben, die davon abhängt, was in den Komponentendetails festgelegt ist. Die Komponenten-Seitenleiste ist mit SidebarActions und SidebarStore verbunden, während Details DetailsActions und DetailsStore sind.

Sie könnten dann AltContainer so verwenden

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

{this.props.content}

Welches würde Geschäfte halten (nun, ich könnte "Geschäft" anstelle von "Geschäfte" Requisite verwenden). Jetzt kann {this.props.content} Details sein, abhängig von der Route. Nehmen wir an, dass / Details uns zu dieser Ansicht weiterleiten. Details hätten zum Beispiel ein Kontrollkästchen, das das Seitenleistenelement von X in Y ändern würde, wenn es aktiviert wäre.

Technisch gibt es keine Beziehung zwischen ihnen und es wäre schwierig, ohne Fluss auszukommen. ABER damit ist es ziemlich einfach.

Kommen wir nun zu DetailsActions. Wir werden dort schaffen

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

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

und DetailsStore

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

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

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

Und jetzt ist dies der Ort, an dem Magie beginnt.

Wie Sie sehen, gibt es bindListener für SidebarActions.ComponentStatusChanged, das verwendet wird, wenn setSiteComponent verwendet wird.

jetzt in SidebarActions

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

Wir haben so etwas. Das Objekt wird auf Abruf versendet. Und es wird aufgerufen, ob setSiteComponent im Store verwendet wird (das Sie in component beispielsweise während onChange on Button oder was auch immer verwenden können).

Jetzt im SidebarStore haben wir

    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;
}

Jetzt können Sie hier sehen, dass es auf DetailsStore wartet. Was bedeutet das? Mehr oder weniger bedeutet dies, dass diese Methode auf das Update von DetailsStoreto warten muss, bevor sie sich selbst aktualisieren kann.

One Store überwacht Methoden in einem Store und löst eine Aktion aus der Komponentenaktion aus, die seinen eigenen Store aktualisiert.

Ich hoffe es kann dir irgendwie helfen.

Shiroo
quelle
0

Wenn Sie Möglichkeiten der Kommunikation zwischen Komponenten erkunden möchten und das Gefühl haben, dass es immer schwieriger wird, sollten Sie ein gutes Entwurfsmuster anwenden: Flux .

Es ist einfach eine Sammlung von Regeln, die definieren, wie Sie den anwendungsweiten Status speichern und mutieren und diesen Status zum Rendern von Komponenten verwenden.

Es gibt viele Flux-Implementierungen, und die offizielle Implementierung von Facebook ist eine davon. Es wird zwar als das angesehen, das den meisten Boilerplate-Code enthält, aber es ist leichter zu verstehen, da die meisten Dinge explizit sind.

Einige andere Alternativen sind Flummox Fluxxor Fluxible und Redux .

Kemal Dağ
quelle
0

Ich war einmal dort, wo Sie gerade sind. Als Anfänger fühlen Sie sich manchmal fehl am Platz, wie Sie darauf reagieren. Ich werde versuchen, genauso vorzugehen, wie ich es mir gerade vorstelle.

Staaten sind der Eckpfeiler für die Kommunikation

Normalerweise kommt es darauf an, wie Sie die Zustände in dieser Komponente ändern, in Ihrem Fall weisen Sie auf drei Komponenten hin.

<List />: In Abhängigkeit von einem Filter wird wahrscheinlich eine Liste mit Elementen angezeigt <Filters />: Filteroptionen, die Ihre Daten ändern. <TopBar />: Liste der Optionen.

Um all diese Interaktionen zu orchestrieren, benötigen Sie eine höhere Komponente. Nennen wir sie App, die Aktionen und Daten an jede dieser Komponenten weitergibt, damit sie beispielsweise so aussehen können

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

Wenn setFilteres aufgerufen wird, wirkt es sich auf das gefilterte Element aus und rendert beide Komponenten neu. Falls dies nicht ganz klar ist, habe ich Ihnen ein Beispiel mit Kontrollkästchen gemacht, das Sie in einer einzelnen Datei einchecken können:

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'));
cabolanoz
quelle
0

Der folgende Code hilft mir beim Einrichten der Kommunikation zwischen zwei Geschwistern. Das Setup wird in ihrem übergeordneten Element während der Aufrufe von render () und componentDidMount () durchgeführt. Es basiert auf https://reactjs.org/docs/refs-and-the-dom.html Ich hoffe, es hilft.

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>
        );
    }
}
Sergei Zinovyev
quelle
Dies ist TypeScript, sollte wahrscheinlich in Ihrer Antwort erwähnt werden. Gutes Konzept.
Serraosays
0

Seltsamerweise erwähnte niemand mobx. Die Idee ist ähnlich wie redux. Wenn ich Daten habe, die von mehreren Komponenten abonniert wurden, kann ich diese Daten verwenden, um mehrere Komponenten zu steuern.

Windmaomao
quelle