ReactJS Due componenti comunicanti


321

Ho appena iniziato con ReactJS e sono un po 'bloccato su un problema che ho.

La mia applicazione è essenzialmente un elenco con filtri e un pulsante per modificare il layout. Al momento sto utilizzando tre componenti: <list />, < Filters />e <TopBar />, ora, ovviamente, quando cambio impostazioni < Filters />voglio innescare qualche metodo in<list /> aggiornare il mio punto di vista.

Come posso fare in modo che questi 3 componenti interagiscano tra loro o ho bisogno di una sorta di modello di dati globale in cui posso semplicemente apportare modifiche?


Tutti e tre i componenti dei fratelli o sono uno dentro l'altro?
pgreen2,

Sono tutti e tre i componenti, ho già riorganizzato la mia applicazione in modo che ora abbiano tutti lo stesso genitore che può fornire loro i dati.
woutr_be,

4
Qui è dove è possibile utilizzare il flusso o il modello pubsub. Sulla base dei documenti nei documenti reagiscono lasciano una frase un po 'ambigua: "Per la comunicazione tra due componenti che non hanno una relazione genitore-figlio, puoi impostare il tuo sistema di eventi globale". facebook.github.io/react/tips/…
BingeBoy

@BingeBoy ha ragione Flux è un ottimo modo per scrivere app di reazioni, in grado di gestire il problema del flusso di dati e della condivisione dei dati da parte di molti componenti.
Ankit Patial,

4
Se non vuoi entrare in Flux o Redux, questo è un fantastico articolo su questo argomento andrewhfarmer.com/component-communication
garajo

Risposte:


318

L'approccio migliore dipende da come si prevede di disporre tali componenti. Alcuni scenari di esempio che vengono subito in mente:

  1. <Filters /> è un componente figlio di <List />
  2. Entrambi <Filters />e <List />sono figli di un componente padre
  3. <Filters />e <List />vivere interamente in componenti root separati.

Potrebbero esserci altri scenari a cui non sto pensando. Se il tuo non rientra in questi, fammi sapere. Ecco alcuni esempi molto approssimativi di come ho gestito i primi due scenari:

Scenario 1

È possibile passare un gestore da <List />a <Filters />, che può quindi essere chiamato onChangesull'evento per filtrare l'elenco con il valore corrente.

JSegnala per # 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);

Scenario n. 2

Simile allo scenario n. 1, ma il componente padre sarà quello che passa la funzione gestore a <Filters />e passerà l'elenco filtrato a <List />. Mi piace questo metodo meglio poiché disaccoppia il <List />da <Filters />.

JSegnala per # 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);

Scenario n. 3

Quando i componenti non sono in grado di comunicare tra qualsiasi tipo di relazione genitore-figlio, la documentazione consiglia di impostare un sistema di eventi globale .


6
La cosa bella del n. 2 è che si affidano solo a un genitore che passa un puntello a ciascun componente: una funzione updateFiltersu <Filters />e un array itemssu <List />. È possibile utilizzare quei componenti figlio in altri genitori con comportamenti diversi, sia insieme che da soli. Ad esempio, se si desidera visualizzare un elenco dinamico ma non è necessario filtrare.
Michael LaCroix,

2
@woutr_be Non siamo sicuri che si adatterà alle tue esigenze ma quando in una situazione simile qualche tempo fa, abbiamo fatto uso delle seguenti due funzioni per ordinare le comunicazioni tra i componenti figlio e genitore: - hearTo: function (eventName, eventCallback) {$ ( window.document) .bind (eventName, eventCallback);} triggerEvent: function (eventName, params) {$ .event.trigger (eventName, params);} Spero che sia d'aiuto! (mi dispiace non poterlo formattare meglio)
5122014009,

29
Per lo scenario 3, esiste un approccio raccomandato? Qualche documento o esempio su questo generando eventi sintetici personalizzati? Non ho trovato nulla nei documenti principali.
pwray,

1
Lo scenario n. 2 ha molto senso ... fino a quando non devi mettere a repentaglio il design (se non altro, il layout) - allora ti rendi conto della necessità di un EventHub / PubSub.
Cody,

4
Il link dello scenario n. 3 è morto e reindirizza ora a una pagina di documenti React non correlata.
Beporter

170

Esistono diversi modi per far comunicare i componenti. Alcuni possono essere adatti al tuo caso d'uso. Ecco un elenco di alcuni che ho trovato utile sapere.

Reagire

Comunicazione diretta genitore / figlio

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

Qui il componente figlio chiamerà un callback fornito dal genitore con un valore e il genitore sarà in grado di ottenere il valore fornito dai figli nel genitore.

Se crei una funzione / pagina della tua app, è meglio avere un unico genitore che gestisce i callback / lo stato (anche chiamato containero smart component) e che tutti i figli siano apolidi, riportando solo cose al genitore. In questo modo puoi facilmente "condividere" lo stato del genitore con qualsiasi figlio che ne abbia bisogno.


Contesto

React Context consente di mantenere lo stato alla radice della gerarchia dei componenti ed essere in grado di iniettare facilmente questo stato in componenti annidati molto profondamente, senza la seccatura di dover passare oggetti di scena a ogni componente intermedio.

Fino ad ora, il contesto era una funzionalità sperimentale, ma in React 16.3 è disponibile una nuova API.

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

Il consumatore sta usando il modello di funzione prop / children render

Controlla questo post sul blog per maggiori dettagli.

Prima di React 16.3, consiglierei di utilizzare la trasmissione broadcast che offre API abbastanza simili e di utilizzare l'API di contesto precedente.


portali

Utilizzare un portale quando si desidera mantenere vicini 2 componenti per farli comunicare con funzioni semplici, come nel normale genitore / figlio, ma non si desidera che questi 2 componenti abbiano una relazione padre / figlio nel DOM, perché dei vincoli visivi / CSS che implica (come z-index, opacità ...).

In questo caso è possibile utilizzare un "portale". Esistono diverse librerie di reazioni che utilizzano portali , generalmente utilizzate per i modali , popup, descrizioni comandi ...

Considera quanto segue:

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

Potrebbe produrre il seguente DOM quando eseguito il rendering all'interno reactAppContainer:

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

Maggiori dettagli qui


slot

Definisci uno slot da qualche parte, quindi riempi lo slot da un altro punto dell'albero di rendering.

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>

Questo è un po 'simile ai portali, tranne per il fatto che il contenuto riempito verrà reso in uno slot definito dall'utente, mentre i portali generalmente renderizzano un nuovo nodo dom (spesso figlio di document.body)

Controlla la libreria di reazione-riempimento-slot


Bus di eventi

Come indicato nella documentazione di React :

Per la comunicazione tra due componenti che non hanno una relazione genitore-figlio, è possibile impostare il proprio sistema di eventi globale. Iscriviti agli eventi in componentDidMount (), annulla l'iscrizione in componentWillUnmount () e chiama setState () quando ricevi un evento.

Ci sono molte cose che puoi usare per configurare un bus eventi. Puoi semplicemente creare un array di listener e, in caso di pubblicazione di eventi, tutti i listener riceveranno l'evento. Oppure puoi usare qualcosa come EventEmitter o PostalJs


Flusso

Flusso è fondamentalmente un bus di eventi, tranne che i ricevitori di eventi sono negozi. Questo è simile al sistema di bus eventi di base, tranne per il fatto che lo stato è gestito al di fuori di React

L'implementazione di Flux originale sembra un tentativo di fare il sourcing degli eventi in modo confuso.

Redux è per me l'implementazione di Flux che è la più vicina al sourcing di eventi, un vantaggio per molti dei vantaggi del sourcing di eventi come la capacità di viaggiare nel tempo. Non è strettamente collegato a React e può essere utilizzato anche con altre librerie di viste funzionali.

Il video tutorial di Redhead di Egghead è davvero bello e spiega come funziona internamente (è davvero semplice).


Cursori

I cursori provengono da ClojureScript / Om e sono ampiamente utilizzati nei progetti React. Consentono di gestire lo stato al di fuori di React e consentono a più componenti di accedere in lettura / scrittura alla stessa parte dello stato, senza che sia necessario sapere nulla sull'albero dei componenti.

Esistono molte implementazioni, tra cui ImmutableJS , React-cursors e Omniscient

Modifica 2016 : sembra che le persone concordino che i cursori funzionano bene per le app più piccole, ma non si adatta bene alle app complesse. Om Next non ha più cursori (mentre è Om che ha introdotto il concetto inizialmente)


Architettura olmo

L' architettura Elm è un'architettura proposta per essere utilizzata dal linguaggio Elm . Anche se Elm non è ReactJS, l'architettura Elm può essere eseguita anche in React.

Dan Abramov, l'autore di Redux, ha realizzato un'implementazione dell'architettura Elm usando React.

Sia Redux che Elm sono davvero fantastici e tendono a potenziare i concetti di approvvigionamento di eventi sul frontend, entrambi consentendo il debug dei viaggi nel tempo, annulla / ripristina, riproduci ...

La differenza principale tra Redux ed Elm è che Elm tende ad essere molto più severo nella gestione dello stato. In Elm non è possibile avere lo stato del componente locale o hook di montaggio / smontaggio e tutte le modifiche al DOM devono essere attivate dalle modifiche dello stato globale. L'architettura Elm propone un approccio scalabile che consente di gestire TUTTO lo stato all'interno di un singolo oggetto immutabile, mentre Redux propone un approccio che invita a gestire la maggior parte dello stato in un singolo oggetto immutabile.

Mentre il modello concettuale di Elm è molto elegante e l'architettura consente di scalare bene su app di grandi dimensioni, in pratica può essere difficile o coinvolgere più boilerplate per realizzare compiti semplici come focalizzare un input dopo averlo montato o integrarsi con una libreria esistente con un'interfaccia imperativa (es. plugin JQuery). Problema correlato .

Inoltre, l'architettura Elm prevede più code di caldaia. Non è così prolisso o complicato da scrivere, ma penso che l'architettura Elm sia più adatta ai linguaggi tipizzati staticamente.


FRP

Librerie come RxJS, BaconJS o Kefir possono essere utilizzate per produrre flussi FRP per gestire la comunicazione tra i componenti.

Puoi provare ad esempio Rx-React

Penso che usare queste librerie sia abbastanza simile all'utilizzo di ciò che il linguaggio ELM offre con i segnali .

Il framework CycleJS non utilizza ReactJS ma utilizza vdom . Condivide molte somiglianze con l'architettura Elm (ma è più facile da usare nella vita reale perché consente gli hook vdom) e utilizza ampiamente gli RxJ invece delle funzioni e può essere una buona fonte di ispirazione se si desidera utilizzare FRP con Reagire. I video di CycleJs Egghead sono belli da capire come funziona.


CSP

I CSP (Comunicating Sequential Processes) sono attualmente popolari (principalmente a causa di Go / goroutines e core.async / ClojureScript) ma puoi usarli anche in javascript con JS-CSP .

James Long ha realizzato un video che spiega come può essere utilizzato con React.

Sagas

Una saga è un concetto di backend che viene dal mondo DDD / EventSourcing / CQRS, chiamato anche "gestore di processo". Viene reso popolare dal progetto redux-saga , principalmente in sostituzione di redux-thunk per la gestione degli effetti collaterali (ad esempio chiamate API ecc.). Molte persone attualmente pensano che serva solo per gli effetti collaterali, ma in realtà si tratta più di disaccoppiare i componenti.

È più un complimento per un'architettura Flux (o Redux) che un sistema di comunicazione totalmente nuovo, perché alla fine la saga emette azioni Flux. L'idea è che se si dispone di widget1 e widget2 e si desidera che siano disaccoppiati, non è possibile attivare l'azione di targeting widget2 da widget1. Quindi fai widget1 attivando solo le azioni che prendono di mira se stesso, e la saga è un "processo in background" che ascolta le azioni di widget1 e può inviare azioni destinate a widget2. La saga è il punto di accoppiamento tra i 2 widget, ma i widget rimangono disaccoppiati.

Se sei interessato dai un'occhiata alla mia risposta qui


Conclusione

Se vuoi vedere un esempio della stessa piccola app usando questi stili diversi, controlla i rami di questo repository .

Non so quale sia l'opzione migliore a lungo termine, ma mi piace molto come Flux assomigli al sourcing di eventi.

Se non conosci concetti di approvvigionamento di eventi, dai un'occhiata a questo blog molto pedagogico: capovolgendo il database con Apache Samza , è una lettura obbligata per capire perché Flux è carino (ma questo potrebbe valere anche per FRP )

Penso che la comunità sia d'accordo sul fatto che l'implementazione di Flux più promettente sia Redux , che consentirà progressivamente un'esperienza di sviluppo molto produttiva grazie alla ricarica a caldo. Impressionante livecoding ala video di Bret Victor Inventing on Principle è possibile!


2
Ottima risposta Seb!
Ali Gajani,

7

OK, ci sono alcuni modi per farlo, ma voglio concentrarmi esclusivamente sull'uso del negozio usando Redux che ti semplifica la vita in queste situazioni piuttosto che darti una soluzione rapida solo per questo caso, usando React puro finirai per rovinare vera e propria grande applicazione e comunicazione tra i componenti diventa sempre più difficile man mano che l'applicazione cresce ...

Quindi cosa fa Redux per te?

Redux è come l'archiviazione locale nella tua applicazione che può essere utilizzata ogni volta che hai bisogno di dati da utilizzare in diversi punti dell'applicazione ...

Fondamentalmente, l'idea di Redux viene originariamente dal flusso, ma con alcuni cambiamenti fondamentali tra cui il concetto di avere una fonte di verità creando un solo negozio ...

Guarda il grafico qui sotto per vedere alcune differenze tra Flux e Redux ...

Redux e Flux

Prendi in considerazione l'applicazione di Redux nella tua applicazione dall'inizio se la tua applicazione necessita di comunicazione tra i Componenti ...

Anche leggere queste parole dalla documentazione di Redux potrebbe essere utile per iniziare con:

Poiché i requisiti per le applicazioni JavaScript a pagina singola sono diventati sempre più complicati, il nostro codice deve gestire più stato che mai . Questo stato può includere le risposte del server e i dati memorizzati nella cache, nonché i dati creati localmente che non sono stati ancora persistiti sul server. Anche lo stato dell'interfaccia utente sta aumentando in complessità, poiché è necessario gestire percorsi attivi, schede selezionate, spinner, controlli di impaginazione e così via.

Gestire questo stato in continua evoluzione è difficile. Se un modello può aggiornare un altro modello, una vista può aggiornare un modello, che aggiorna un altro modello e questo, a sua volta, potrebbe causare l'aggiornamento di un'altra vista. Ad un certo punto, non capisci più cosa succede nella tua app poiché hai perso il controllo su quando, perché e come del suo stato. Quando un sistema è opaco e non deterministico, è difficile riprodurre bug o aggiungere nuove funzionalità.

Come se ciò non bastasse, considera i nuovi requisiti diventare comuni nello sviluppo del prodotto front-end. Come sviluppatori, ci si aspetta che gestiamo aggiornamenti ottimistici, rendering sul lato server, recupero di dati prima di eseguire transizioni di route e così via. Ci troviamo a cercare di gestire una complessità che non abbiamo mai avuto a che fare prima e inevitabilmente ci poniamo la domanda: è tempo di arrendersi? La risposta è no.

Questa complessità è difficile da gestire poiché stiamo mescolando due concetti che sono molto difficili da ragionare per la mente umana: mutazione e asincronicità. Li chiamo Mentos e Coca-Cola. Entrambi possono essere fantastici nella separazione, ma insieme creano un casino. Le librerie come React tentano di risolvere questo problema nel livello di visualizzazione rimuovendo sia la sincronizzazione asincrona che la manipolazione DOM diretta. Tuttavia, la gestione dello stato dei dati è lasciata a te. È qui che entra Redux.

Seguendo le fasi di Flux, CQRS ed Event Sourcing , Redux tenta di rendere prevedibili le mutazioni di stato imponendo alcune restrizioni su come e quando possono avvenire gli aggiornamenti . Queste restrizioni si riflettono nei tre principi di Redux.


Come può aiutare Redux ? se ho un modale per un datepicker(come componente) e quel componente può essere caricato da più componenti che vivono sulla stessa pagina, come farebbe il datepickercomponente a sapere quale azione inviare per il redux? Questa è l'essenza del problema, che collega un'azione in un componente a un altro e NON a nessun altro componente . (prendere in considerazione lo datepickerstesso è un componente profondo, profondo all'interno del componente modale stesso)
vsync

@vsync non pensa reudx come un singolo valore statico, redux è in realtà può avere oggetti, array ... quindi puoi salvarli come oggetto o array o qualunque cosa nel tuo negozio, può essere mapdispatchtoprops e ognuno viene salvato nell'array di oggetti come: [{name: "picker1", valore: "01/01/1970"}, {name: "picker2", valore: "01/01/1980"}] e quindi usa mapstatetoprops in parent e passalo a ogni componente o dove vuoi, non sei sicuro che risponda alla tua domanda, ma senza vedere il codice ... se sono in gruppi separati, puoi anche obiettare con più dettagli ... ma dipende tutto da come vuoi raggruppare loro ..
Alireza,

La domanda non riguarda reduxe cosa è possibile memorizzare, ma COME passare l'azione in profondità a ciò che deve attivarla. Come fa un componente profondo a sapere cosa deve essere attivato ESATTAMENTE ? poiché nell'esempio ho fornito un componente generale che dovrebbe innescare un riduttore specifico, a seconda dello scenario, quindi potrebbe essere un riduttore diverso poiché un modal datapicker può essere utilizzato per qualsiasi componente.
vsync,

5

Questo è il modo in cui l'ho gestito.
Supponiamo che tu abbia <select> per Month e <select> for Day . Il numero di giorni dipende dal mese selezionato.

Entrambi gli elenchi sono di proprietà di un terzo oggetto, il pannello di sinistra. Entrambi <select> sono anche figli di leftPanel <div>
È un gioco con i callback e i gestori nel componente LeftPanel .

Per provarlo, basta copiare il codice in due file separati ed eseguire index.html . Quindi selezionare un mese e vedere come cambia il numero di giorni.

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

E l'HTML per l'esecuzione del componente index.html del pannello di sinistra

<!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>

se potessi eliminare l'80% del codice di esempio e mantenere comunque il tuo punto, sarebbe meglio. mostrando CSS nel contesto di questa discussione è irrilevante
vsync

3

Ho visto che la domanda ha già una risposta, ma se desideri saperne di più dettagli, ci sono un totale di 3 casi di comunicazione tra i componenti :

  • Caso 1: comunicazione da genitore a figlio
  • Caso 2: comunicazione da figlio a genitore
  • Caso 3: comunicazione di componenti non correlati (da qualsiasi componente a qualsiasi componente)

1

Estendendo la risposta di @MichaelLaCroix quando uno scenario è che i componenti non sono in grado di comunicare tra qualsiasi tipo di relazione genitore-figlio, la documentazione raccomanda di impostare un sistema di eventi globale.

Nel caso di <Filters />e <TopBar />senza alcuna delle precedenti relazioni, un semplice emettitore globale potrebbe essere usato in questo modo:

componentDidMount - Iscriviti all'evento

componentWillUnmount - Annulla l'iscrizione all'evento

React.js e codice EventSystem

EventSystem.js

class EventSystem{

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

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

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

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

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

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

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

        return true;
    }

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

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

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

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

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

        return [namespace, callback];
    }

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

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

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

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

}

NotificationComponent.js

class NotificationComponent extends React.Component {

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

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

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

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

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

    render() {

    }
}

0

Esiste tale possibilità anche se non sono genitori - relazione figlio - e questo è Flux. C'è un'implementazione abbastanza buona (per me personalmente) per quella chiamata Alt.JS (con Alt-Container).

Ad esempio, puoi avere la barra laterale che dipende da ciò che è impostato nei dettagli del componente. La barra laterale dei componenti è connessa con SidebarActions e SidebarStore, mentre Details è DetailsActions e DetailsStore.

È quindi possibile utilizzare AltContainer in questo modo

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

{this.props.content}

Che manterrebbe i negozi (beh, potrei usare "store" invece di "store" prop). Ora, {this.props.content} PUO 'ESSERE Dettagli a seconda del percorso. Diciamo che / Dettagli ci reindirizza a quella vista. I dettagli avrebbero ad esempio una casella di controllo che cambierebbe l'elemento della barra laterale da X a Y se fosse selezionato.

Tecnicamente non esiste alcuna relazione tra loro e sarebbe difficile fare a meno del flusso. MA CON QUESTO è piuttosto facile.

Ora andiamo a DetailsActions. Creeremo lì

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

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

e Dettagli Store

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

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

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

E ora, questo è il posto dove inizia la magia.

Come puoi vedere, c'è bindListener in SidebarActions.ComponentStatusChanged che verrà utilizzato SE verrà utilizzato setSiteComponent.

ora in SidebarActions

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

Abbiamo una cosa del genere. Spedirà quell'oggetto su chiamata. E verrà chiamato se verrà utilizzato setSiteComponent in store (che è possibile utilizzare nel componente ad esempio durante onChange sul pulsante o altro)

Ora in SidebarStore avremo

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

Ora qui puoi vedere che attenderà DetailsStore. Cosa significa? più o meno significa che questo metodo deve attendere l'aggiornamento di DetailsStoreto prima che possa aggiornarsi.

tl; dr One Store è in ascolto dei metodi in un negozio e attiverà un'azione dall'azione componente, che aggiornerà il proprio negozio.

Spero che possa aiutarti in qualche modo.


0

Se vuoi esplorare le opzioni di comunicazione tra i componenti e ritieni che stia diventando sempre più difficile, potresti prendere in considerazione l'adozione di un buon modello di progettazione: Flux .

È semplicemente una raccolta di regole che definisce il modo in cui archiviare e mutare lo stato dell'intera applicazione e utilizzarlo per eseguire il rendering dei componenti.

Esistono molte implementazioni Flux e l'implementazione ufficiale di Facebook è una di queste. Sebbene sia considerato quello che contiene la maggior parte del codice boilerplate, ma è più facile da capire poiché la maggior parte delle cose sono esplicite.

Alcune delle Altre alternative sono flummox fluxxor fluxible e redux .


0

Una volta ero dove sei adesso, come principiante a volte ti senti fuori posto su come reagire. Proverò ad affrontare lo stesso modo in cui ci penso adesso.

Gli stati sono la pietra angolare della comunicazione

Di solito ciò che si riduce è il modo in cui si modificano gli stati di questo componente nel caso in cui si evidenzino tre componenti.

<List />: Che probabilmente visualizzerà un elenco di elementi in base a un filtro <Filters />: Opzioni di filtro che modificheranno i tuoi dati. <TopBar />: Elenco di opzioni.

Per orchestrare tutta questa interazione avrai bisogno di un componente superiore chiamiamolo App, che passerà azioni e dati a ciascuno di questi componenti in modo che, ad esempio, possa apparire così

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

Quindi, quando setFilterviene chiamato, influirà su filteredItem e ri-renderizzerà entrambi i componenti ;. Nel caso in cui ciò non sia del tutto chiaro, ti ho fatto un esempio con la casella di controllo che puoi controllare in un singolo file:

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

Il seguente codice mi aiuta a configurare la comunicazione tra due fratelli. L'installazione viene eseguita nel loro genitore durante le chiamate render () e componentDidMount (). Si basa su https://reactjs.org/docs/refs-and-the-dom.html Spero che sia d'aiuto.

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

Questo è TypeScript, probabilmente dovrebbe essere menzionato nella tua risposta. Buona idea però.
serraosays,

0

Stranamente nessuno ha menzionato mobx. L'idea è simile a redux. Se ho un pezzo di dati a cui sono iscritti più componenti, posso usare questi dati per guidare più componenti.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.