ReactJS Komunikacja dwóch komponentów

321

Właśnie zacząłem pracę z ReactJS i trochę utknąłem w moim problemie.

Moja aplikacja to zasadniczo lista z filtrami i przycisk do zmiany układu. W tej chwili używam trzy składniki: <list />, < Filters />a <TopBar />teraz oczywiście przy zmianie ustawień < Filters />Chcę wywołać jakąś metodę w <list />celu zaktualizowania mój pogląd.

Jak sprawić, by te 3 komponenty współdziałały ze sobą, czy też potrzebuję jakiegoś globalnego modelu danych, w którym mogę po prostu wprowadzić zmiany?

woutr_be
źródło
Czy wszystkie trzy elementy rodzeństwa są jedne w drugich?
pgreen2
Są to wszystkie trzy elementy, już zmieniłem układ mojej aplikacji, aby teraz wszyscy mieli tego samego rodzica, który może dostarczyć im dane.
woutr_be
4
Tutaj możesz użyć wzoru flux lub pubsub. W oparciu o dokumenty w dokumentach reagujących pozostawiają nieco dwuznaczne zdanie: „W celu komunikacji między dwoma komponentami, które nie mają relacji rodzic-dziecko, możesz skonfigurować własny globalny system zdarzeń”. facebook.github.io/react/tips/…
BingeBoy
@ BingeBoy ma rację Flux to świetny sposób na pisanie aplikacji Reagjs, które mogą poradzić sobie z problemem przepływu danych, udostępniania danych przez wiele komponentów.
Ankit Patial,
4
Jeśli nie chcesz wchodzić w Flux ani Redux, jest to niesamowity artykuł na ten temat andrewhfarmer.com/component-communication
garajo

Odpowiedzi:

318

Najlepsze podejście zależy od tego, jak planujesz rozmieścić te komponenty. Kilka przykładowych scenariuszy, które przychodzą teraz na myśl:

  1. <Filters /> jest elementem potomnym <List />
  2. Zarówno <Filters />i <List />są dziećmi elementu nadrzędnego
  3. <Filters />i <List />żyją całkowicie w oddzielnych składnikach głównych.

Mogą istnieć inne scenariusze, o których nie myślę. Jeśli twoje nie mieści się w tych, daj mi znać. Oto kilka bardzo przybliżonych przykładów radzenia sobie z dwoma pierwszymi scenariuszami:

Scenariusz nr 1

Można przekazać moduł obsługi od <List />do <Filters />, który można następnie wywołać w onChangezdarzeniu w celu przefiltrowania listy przy użyciu bieżącej wartości.

JSFiddle dla # 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);

Scenariusz nr 2

Podobnie jak w scenariuszu nr 1, ale komponent nadrzędny będzie tym, który przekazuje funkcję modułu obsługi do <Filters />i przekaże przefiltrowaną listę do <List />. Bardziej podoba mi się ta metoda, ponieważ oddziela ją <List />od <Filters />.

JSFiddle dla # 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);

Scenariusz nr 3

Gdy komponenty nie mogą komunikować się między jakimkolwiek rodzajem relacji rodzic-dziecko, dokumentacja zaleca skonfigurowanie globalnego systemu zdarzeń .

Michael LaCroix
źródło
6
Zaletą z # 2 jest to, że tylko polegać na rodzica, który przechodzi rekwizyt do każdej części: funkcja jak updateFilterdo <Filters />i szyku, jak itemssię <List />. Możesz użyć tych elementów potomnych u innych rodziców o różnych zachowaniach, razem lub solo. Na przykład, jeśli chcesz wyświetlić listę dynamiczną, ale nie potrzebujesz filtrowania.
Michael LaCroix
2
@woutr_be Nie jestem pewien, czy pasowałby do twoich wymagań, ale kiedy w podobnej sytuacji jakiś czas temu, skorzystaliśmy z następujących dwóch funkcji, aby w pewnym sensie komunikować się między komponentami potomnymi i nadrzędnymi: - listenTo: function (eventName, eventCallback) {$ ( window.document) .bind (eventName, eventCallback);} triggerEvent: function (eventName, params) {$ .event.trigger (eventName, params);} Mam nadzieję, że to pomaga! (przepraszam, nie mogłem lepiej sformatować)
5122014009 24.01.14
29
Czy w przypadku scenariusza 3 istnieje zalecane podejście? Jakieś dokumenty lub przykłady na ten temat, generując niestandardowe syntetyczne zdarzenia? Nie znalazłem niczego w głównych dokumentach.
pwray
1
Scenariusz nr 2 ma wiele sensu ... dopóki nie narazisz projektu (jeśli tylko na układ) - wtedy zdasz sobie sprawę z konieczności utworzenia EventHub / PubSub.
Cody
4
Link do scenariusza nr 3 jest martwy i przekierowuje do niepowiązanej strony z dokumentacją React.
beporter
170

Istnieje wiele sposobów komunikacji między komponentami. Niektóre mogą być dostosowane do twojej skrzynki użytkownika. Oto lista niektórych, które uznałem za przydatne.

Reagować

Bezpośrednia komunikacja rodzic / dziecko

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

W tym przypadku komponent potomny wywoła wywołanie zwrotne dostarczone przez rodzica z wartością, a rodzic będzie mógł uzyskać wartość dostarczoną przez dzieci w rodzicu.

Jeśli tworzysz funkcję / stronę swojej aplikacji, lepiej jest mieć samotnego rodzica zarządzającego wywołaniami zwrotnymi / stanem (zwanym także containerlub smart component), a wszystkie dzieci muszą być bezpaństwowcami. W ten sposób możesz łatwo „udostępnić” stan rodzica każdemu dziecku, które tego potrzebuje.


Kontekst

Kontekst React pozwala na zachowanie stanu w katalogu głównym hierarchii komponentów i umożliwia łatwe wstrzykiwanie tego stanu do bardzo głęboko zagnieżdżonych komponentów, bez konieczności przekazywania rekwizytów do wszystkich komponentów pośrednich.

Do tej pory kontekst był funkcją eksperymentalną, ale nowy interfejs API jest dostępny w 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>
);

Konsument używa wzorca renderowania / funkcji potomnych

Sprawdź ten post na blogu, aby uzyskać więcej informacji.

Przed React 16.3, zalecałbym używanie transmisji telewizyjnej, która oferuje dość podobny interfejs API i używanie wcześniejszego kontekstowego interfejsu API.


Portale

Skorzystaj z portalu, jeśli chcesz trzymać dwa komponenty blisko siebie, aby komunikować się za pomocą prostych funkcji, takich jak w normalnym obiekcie nadrzędnym / podrzędnym, ale nie chcesz, aby te 2 składniki miały relację nadrzędny / podrzędny w DOM, ponieważ implikowanych ograniczeń wizualnych / CSS (takich jak indeks Z, krycie ...).

W takim przypadku możesz użyć „portalu”. Istnieją różne biblioteki reagujące za pomocą portali , zwykle używanych do modałów , wyskakujących okienek, podpowiedzi ...

Rozważ następujące:

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

Po wygenerowaniu może wygenerować następujący DOM reactAppContainer:

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

Więcej informacji tutaj


Automaty

Definiujesz gdzieś szczelinę, a następnie wypełniasz ją z innego miejsca drzewa renderowania.

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>

Jest to trochę podobne do portali, z tym że wypełniona zawartość będzie renderowana w zdefiniowanym gnieździe, podczas gdy portale generalnie renderują nowy węzeł dom (często dzieci dokumentu.body)

Sprawdź bibliotekę reaguj-zapełnij szczelinę


Autobus zdarzeń

Jak stwierdzono w dokumentacji React :

Do komunikacji między dwoma komponentami, które nie mają relacji rodzic-dziecko, możesz skonfigurować własny globalny system zdarzeń. Subskrybuj zdarzenia w module componentDidMount (), anuluj subskrypcję w module componentWillUnmount () i wywołaj setState () po odebraniu zdarzenia.

Istnieje wiele rzeczy, których możesz użyć do skonfigurowania magistrali zdarzeń. Możesz po prostu utworzyć tablicę detektorów, a po opublikowaniu zdarzenia wszyscy detektory otrzymaliby zdarzenie. Lub możesz użyć czegoś takiego jak EventEmitter lub PostalJs


Strumień

Strumień jest zasadniczo magistralą zdarzeń, z tym wyjątkiem, że odbiorniki zdarzeń są sklepami. Jest to podobne do podstawowego systemu magistrali zdarzeń, z tym wyjątkiem, że stan jest zarządzany poza React

Oryginalna implementacja Fluxa wygląda jak próba hackowania Event-sourcingu.

Redux jest dla mnie implementacją Flux, która jest najbliższa pozyskiwaniu zdarzeń, ma wiele zalet związanych z pozyskiwaniem zdarzeń, takich jak możliwość podróżowania w czasie. Nie jest ściśle powiązany z React i może być również używany z innymi bibliotekami widoków funkcjonalnych.

Samouczek wideo Redhead firmy Egghead jest naprawdę fajny i wyjaśnia, jak działa wewnętrznie (jest naprawdę prosty).


Kursory

Kursory pochodzą z ClojureScript / Om i są szeroko stosowane w projektach React. Pozwalają zarządzać stanem poza React i pozwalają wielu komponentom na dostęp do odczytu / zapisu do tej samej części stanu, bez konieczności posiadania wiedzy na temat drzewa komponentów.

Istnieje wiele implementacji, w tym ImmutableJS , React-cursors i Omniscient

Edycja 2016 : wydaje się, że ludzie zgadzają się, że kursory działają dobrze w przypadku mniejszych aplikacji, ale nie skaluje się dobrze w złożonych aplikacjach. Om Next nie ma już kursorów (chociaż to Om początkowo wprowadził tę koncepcję)


Architektura wiązu

Architektura Elm to architektura zaproponowała być wykorzystywane przez języku Elm . Nawet jeśli Elm nie jest ReactJS, architektura Elm może być również wykonana w React.

Dan Abramov, autor Redux, wykonał implementację architektury Elm przy użyciu React.

Zarówno Redux, jak i Elm są naprawdę świetne i mają tendencję do wzmacniania koncepcji pozyskiwania zdarzeń na froncie, umożliwiając debugowanie w czasie, cofanie / ponawianie, odtwarzanie ...

Główna różnica między Redux a Elm polega na tym, że Elm jest o wiele bardziej rygorystyczny w zarządzaniu stanem. W Elm nie możesz mieć stanu lokalnego komponentu ani haków montowania / odmontowywania, a wszystkie zmiany DOM muszą być wywoływane przez globalne zmiany stanu. Architektura wiązów proponuje skalowalne podejście, które pozwala obsłużyć WSZYSTKIE stany wewnątrz jednego niezmiennego obiektu, podczas gdy Redux proponuje podejście, które zaprasza do obsługi WIĘKSZOŚCI stanu w pojedynczym niezmiennym obiekcie.

Podczas gdy koncepcyjny model Elm jest bardzo elegancki, a architektura pozwala dobrze skalować się w dużych aplikacjach, w praktyce może być trudny lub wymagać większej liczby elementów do wykonania prostych zadań, takich jak skupienie się na danych wejściowych po zamontowaniu lub integracja z istniejącą biblioteką z interfejsem imperatywnym (tj. wtyczką JQuery). Powiązany problem .

Również architektura Elm wymaga większej liczby kodów. Pisanie nie jest takie pełne i skomplikowane, ale myślę, że architektura Elm jest bardziej odpowiednia dla języków o typie statycznym.


FRP

Biblioteki takie jak RxJS, BaconJS lub Kefir mogą być używane do tworzenia strumieni FRP do obsługi komunikacji między komponentami.

Możesz spróbować na przykład Rx-React

Myślę, że używanie tych bibliotek jest dość podobne do używania tego, co oferuje język ELM z sygnałami .

Struktura CycleJS nie używa ReactJS, ale używa vdom . Ma wiele podobieństw z architekturą Elm (ale jest łatwiejszy w użyciu, ponieważ pozwala na haczyki vdom) i używa RxJs intensywnie zamiast funkcji i może być dobrym źródłem inspiracji, jeśli chcesz używać FRP z Reagować. Filmy z CycleJs Egghead są miłe, aby zrozumieć, jak to działa.


CSP

CSP (Communicating Sequential Processes) są obecnie popularne (głównie ze względu na Go / goroutines i core.async / ClojureScript), ale można ich używać również w javascript z JS-CSP .

James Long nakręcił film wyjaśniający, jak można go używać z React.

Sagi

Saga to koncepcja zaplecza, która pochodzi ze świata DDD / EventSourcing / CQRS, zwana także „menedżerem procesów”. Jest popularyzowany przez projekt redux-saga , głównie jako zamiennik redux-thunk do obsługi efektów ubocznych (tj. Wywołań API itp.). Większość ludzi uważa obecnie, że służy to jedynie do działań niepożądanych, ale tak naprawdę chodzi raczej o oddzielenie komponentów.

Jest to raczej komplement dla architektury Flux (lub Redux) niż całkowicie nowy system komunikacji, ponieważ saga emituje akcje Flux na końcu. Chodzi o to, że jeśli masz widget1 i widget2 i chcesz, aby były one oddzielone, nie możesz strzelać do akcji widget2 z widget1. Dzięki temu widżet1 wykonuje tylko te akcje strzelania, które są skierowane na siebie, a saga jest „procesem w tle”, który nasłuchuje akcji widget1 i może wywoływać akcje skierowane na widget2. Saga jest punktem łączącym 2 widżety, ale widżety pozostają oddzielone.

Jeśli jesteś zainteresowany, spójrz na moją odpowiedź tutaj


Wniosek

Jeśli chcesz zobaczyć przykład tej samej małej aplikacji używającej tych różnych stylów, sprawdź gałęzie tego repozytorium .

Nie wiem, która opcja jest najlepsza na dłuższą metę, ale bardzo podoba mi się to, jak Flux wygląda jak pozyskiwanie zdarzeń.

Jeśli nie znasz koncepcji pozyskiwania zdarzeń , zapoznaj się z tym bardzo pedagogicznym blogiem: Przekształcanie bazy danych na lewą stronę za pomocą apache Samza , należy przeczytać, aby zrozumieć, dlaczego Flux jest fajny (ale może to również dotyczyć FRP )

Myślę, że społeczność zgadza się, że najbardziej obiecującą implementacją Fluxa jest Redux , który stopniowo zapewni bardzo produktywne doświadczenie programistyczne dzięki gorącemu przeładowaniu. Imponujące kodowanie na żywo ala Bret Victor's Inventing on Principle jest możliwe!

Sebastien Lorber
źródło
2
Doskonała odpowiedź Seb!
Ali Gajani,
7

OK, jest kilka sposobów, aby to zrobić, ale chcę skupić się wyłącznie na korzystaniu ze sklepu za pomocą Redux, co znacznie ułatwi ci życie w takich sytuacjach, zamiast dać ci szybkie rozwiązanie tylko w tym przypadku, użycie czystego React skończy się bałaganem w naprawdę duża aplikacja i komunikacja między komponentami staje się coraz trudniejsza wraz z rozwojem aplikacji ...

Więc co Redux robi dla ciebie?

Redux przypomina lokalną pamięć w aplikacji, z której można korzystać zawsze, gdy potrzebujesz danych do wykorzystania w różnych miejscach aplikacji ...

Zasadniczo pomysł Redux pochodzi z flux pierwotnie, ale z pewnymi fundamentalnymi zmianami, w tym koncepcją posiadania jednego źródła prawdy poprzez stworzenie tylko jednego sklepu ...

Spójrz na poniższy wykres, aby zobaczyć różnice między Flux i Redux ...

Redux and Flux

Rozważ zastosowanie Redux w swojej aplikacji od samego początku, jeśli aplikacja wymaga komunikacji między komponentami ...

Pomocne może być również przeczytanie tych słów z Dokumentacji Redux na początek:

Ponieważ wymagania dotyczące aplikacji jednostronicowych JavaScript stają się coraz bardziej skomplikowane, nasz kod musi zarządzać większą liczbą stanów niż kiedykolwiek wcześniej . Ten stan może obejmować odpowiedzi serwera i buforowane dane, a także lokalnie utworzone dane, które nie zostały jeszcze utrwalone na serwerze. Stan interfejsu użytkownika również rośnie w złożoności, ponieważ musimy zarządzać aktywnymi trasami, wybranymi kartami, pokrętłami, kontrolkami paginacji i tak dalej.

Zarządzanie tym ciągle zmieniającym się stanem jest trudne. Jeśli model może zaktualizować inny model, widok może zaktualizować model, który zaktualizuje inny model, a to z kolei może spowodować aktualizację innego widoku. W pewnym momencie nie rozumiesz już, co dzieje się w Twojej aplikacji, ponieważ straciłeś kontrolę nad tym, kiedy, dlaczego i jaki jest jej stan. Gdy system jest nieprzejrzysty i niedeterministyczny, trudno jest odtworzyć błędy lub dodać nowe funkcje.

Jakby to nie było dość złe, zastanów się, czy nowe wymagania stają się powszechne w rozwoju produktów front-end. Jako programiści oczekujemy optymistycznych aktualizacji, renderowania po stronie serwera, pobierania danych przed wykonaniem przejścia trasy i tak dalej. Próbujemy poradzić sobie ze złożonością, z którą nigdy wcześniej nie mieliśmy do czynienia, i nieuchronnie zadajemy pytanie: czy czas się poddać? Odpowiedź brzmi nie.

Ta złożoność jest trudna do opanowania, ponieważ mieszamy dwie koncepcje, o których ludzki umysł bardzo trudno się zastanowić: mutacja i asynchroniczność. Nazywam je Mentos i Coke. Oba mogą być świetne w separacji, ale razem tworzą bałagan. Biblioteki takie jak React próbują rozwiązać ten problem w warstwie widoku poprzez usunięcie zarówno asynchronicznej, jak i bezpośredniej manipulacji DOM. Jednak zarządzanie stanem twoich danych należy do ciebie. Tutaj wkracza Redux.

Postępując zgodnie z krokami Flux, CQRS i Event Sourcing , Redux próbuje uczynić mutacje stanu przewidywalnymi, nakładając pewne ograniczenia na to, jak i kiedy mogą się odbywać aktualizacje . Ograniczenia te znajdują odzwierciedlenie w trzech zasadach Redux.

Alireza
źródło
W jaki sposób redux może pomóc? jeśli mam modal dla datepicker(jako komponentu) i ten komponent można załadować z wielu komponentów mieszkających na tej samej stronie, to skąd datepickerkomponent wiedziałby, którą akcję wysłać do redux? To jest istota problemu, łącząc działanie w jednym składniku BTO inny i nie każdy inny komponent . (weź pod uwagę, że datepickersam jest głębokim, głębokim składnikiem w samym składniku modalnym)
vsync
@vsync nie sądzi, że reudx jest pojedynczą wartością statyczną, redux faktycznie może zawierać obiekty, tablice ... więc możesz zapisać je jako obiekt lub tablicę lub cokolwiek w sklepie, może to być mapdispatchtoprops i każdy zapisany w tablicy obiektów jak: [{nazwa: „picker1”, wartość: „01/01/1970”}, {nazwa: „picker2”, wartość: „01/01/1980”}], a następnie użyj mapstatetoprops jako elementu nadrzędnego i przekaż go do każdy komponent lub gdziekolwiek chcesz, nie jestem pewien, czy odpowiada na twoje pytanie, ale nie widząc kodu ... jeśli są w osobnych grupach, możesz również sprzeciwić się więcej szczegółów ... ale wszystko zależy od tego, jak chcesz grupować im ..
Alireza,
Pytanie nie dotyczy reduxi tego, co możesz przechowywać, ale JAK przekazać akcję głęboko w dół do tego, co musi ją uruchomić. Skąd głęboki składnik wie, co DOKŁADNIE należy uruchomić? ponieważ w tym przykładzie podałem ogólny komponent, który powinien wyzwalać określony reduktor, w zależności od scenariusza, więc może to być inny reduktor, ponieważ dla każdego komponentu można zastosować modal datepicker .
vsync,
5

Tak sobie z tym poradziłem.
Załóżmy, że masz <select> na miesiąc i <select> na dzień . Liczba dni zależy od wybranego miesiąca.

Obie listy są własnością trzeciego obiektu, lewego panelu. Zarówno <select> są również dziećmi leftPanel <div>
Jest to gra z wywołaniami zwrotnymi i programami obsługi w LeftPanel komponencie .

Aby to przetestować, po prostu skopiuj kod do dwóch oddzielnych plików i uruchom plik index.html . Następnie wybierz miesiąc i zobacz, jak zmienia się liczba dni.

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

I HTML do uruchamiania lewego panelu 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
źródło
jeśli możesz usunąć 80% przykładowego kodu i nadal zachować swój punkt widzenia, byłoby najlepiej. pokazanie CSS w kontekście tego wątku jest nieistotne
vsync
3

Widziałem, że na pytanie już udzielono odpowiedzi, ale jeśli chcesz dowiedzieć się więcej szczegółów, istnieją w sumie 3 przypadki komunikacji między komponentami :

  • Przypadek 1: Komunikacja rodzic-dziecko
  • Przypadek 2: Komunikacja między rodzicami
  • Przypadek 3: Komunikacja z komponentami niepowiązanymi (dowolny komponent z dowolnym komponentem)
Kaloyan Kosev
źródło
1

Rozszerzając odpowiedź @MichaelLaCroix, gdy w scenariuszu komponenty nie mogą komunikować się między jakimkolwiek rodzicielskim stosunkiem-dzieckiem, dokumentacja zaleca ustanowienie globalnego systemu zdarzeń.

W przypadku <Filters />i<TopBar /> nie mają żadnego związku z powyższym, prosty globalny emiter może być stosowany tak:

componentDidMount - Subskrybuj wydarzenie

componentWillUnmount - Anuluj subskrypcję wydarzenia

Kod React.js i 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() {

    }
}
Tsuz
źródło
0

Istnieje taka możliwość, nawet jeśli nie są relacjami rodzic - dziecko - i to jest Flux. Istnieje całkiem dobra (dla mnie osobiście) implementacja tego o nazwie Alt.JS (z Alt-Container).

Na przykład możesz mieć pasek boczny, który zależy od tego, co jest ustawione w szczegółach komponentu. Komponentowy pasek boczny jest połączony z SidebarActions i SidebarStore, natomiast Details to DetailsActions i DetailsStore.

Możesz użyć AltContainer w ten sposób

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

{this.props.content}

Który trzymałby sklepy (no cóż, mógłbym użyć propozycji „store” zamiast „store”). Teraz {this.props.content} MOŻE BYĆ Szczegóły w zależności od trasy. Powiedzmy, że / Details przekierowuje nas do tego widoku. Szczegóły zawierałyby na przykład pole wyboru, które zmieniałoby element paska bocznego z X na Y, gdyby był zaznaczony.

Technicznie nie ma między nimi żadnego związku i trudno byłoby obejść się bez przepływu. ALE Z TYM, że jest to raczej łatwe.

Przejdźmy teraz do DetailsActions. Stworzymy tam

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

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

i DetailsStore

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

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

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

A teraz tutaj zaczyna się magia.

Jak widać, istnieje SideLAdener do SidebarActions.ComponentStatusChanged, który zostanie użyty JEŻELI zostanie użyty setSiteComponent.

teraz w SidebarActions

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

Mamy coś takiego. Wyśle ten obiekt na wezwanie. Zostanie on wywołany, jeśli zostanie użyty setSiteComponent w sklepie (którego można użyć w komponencie na przykład podczas przełączania onChange na przycisku itp.)

Teraz w SidebarStore będziemy mieli

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

Teraz możesz zobaczyć, że będzie czekać na DetailsStore. Co to znaczy? mniej więcej oznacza to, że ta metoda musi poczekać na aktualizację DetailsStoreto, zanim będzie mogła się zaktualizować.

tl; dr One Store nasłuchuje metod w sklepie i wyzwoli akcję z akcji komponentu, która zaktualizuje swój własny sklep.

Mam nadzieję, że może ci to jakoś pomóc.

Shiroo
źródło
0

Jeśli chcesz zbadać opcje komunikacji między komponentami i poczujesz, że staje się to coraz trudniejsze, możesz rozważyć zastosowanie dobrego wzorca projektowego: Flux .

Jest to po prostu zbiór reguł, które definiują sposób przechowywania i mutowania stanu aplikacji i używania go do renderowania komponentów.

Istnieje wiele implementacji Flux, a oficjalna implementacja Facebooka jest jedną z nich. Chociaż jest uważany za ten, który zawiera większość kodu typu „Boilerplate”, ale łatwiej go zrozumieć, ponieważ większość rzeczy jest jawna.

Niektóre inne alternatywy są speszyć kogoś fluxxor fluxible i Redux .

Kemal Dağ
źródło
0

Kiedyś byłem tam, gdzie jesteś teraz, jako początkujący czasami czujesz się nie na miejscu, jak zareagować, aby to zrobić. Spróbuję rozwiązać ten sam sposób, w jaki teraz o tym myślę.

Stany są kamieniem węgielnym komunikacji

Zwykle sprowadza się to do sposobu zmiany stanów w tym komponencie, w twoim przypadku wskazujesz trzy komponenty.

<List />: Który prawdopodobnie wyświetli listę elementów w zależności od filtra <Filters />: Opcje filtrów, które zmienią twoje dane. <TopBar />: Lista opcji.

Aby zaaranżować całą tę interakcję, będziesz potrzebować wyższego komponentu nazwijmy go aplikacją, która przekaże akcje i dane do każdego z tych komponentów, aby na przykład wyglądać tak

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

Kiedy setFilterzostanie wywołany, wpłynie to na filterItem i ponownie wyrenderuje oba składniki ;. W przypadku, gdy nie jest to do końca jasne, podałem przykład z polem wyboru, w którym można wpisać jeden plik:

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'));
kabolanoz
źródło
0

Poniższy kod pomaga mi skonfigurować komunikację między dwojgiem rodzeństwa. Konfiguracja odbywa się nadrzędnie podczas wywołań render () i componentDidMount (). Jest oparty na https://reactjs.org/docs/refs-and-the-dom.html Mam nadzieję, że to pomaga.

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>
        );
    }
}
Siergiej Zinowjew
źródło
To jest TypeScript, prawdopodobnie należy wspomnieć w swojej odpowiedzi. Dobra koncepcja.
serraosays
0

Dziwnie nikt nie wspomniał mobx. Pomysł jest podobny do redux. Jeśli mam fragment danych subskrybowanych przez wiele komponentów, mogę użyć tych danych do sterowania wieloma komponentami.

windmaomao
źródło