web-dev-qa-db-ja.com

ReactJS 2つのコンポーネント間の通信

私はReactJSを始めたばかりで、私が抱えている問題に少し立ち往生しています。

私のアプリケーションは基本的にフィルタとレイアウトを変更するためのボタンを備えたリストです。現時点では、<list />< Filters />、および<TopBar />の3つのコンポーネントを使用しています。< Filters />の設定を変更したときには、明らかにビューを更新するために<list />のメソッドをトリガーします。

これら3つのコンポーネントを互いに対話させるにはどうすればよいですか。それとも、単に変更を加えることができるような、何らかのグローバルデータモデルが必要ですか?

284
woutr_be

最善の方法は、それらのコンポーネントをどのように配置するかによって異なります。今頭に浮かぶいくつかのシナリオ例:

  1. <Filters /><List />の子コンポーネントです
  2. <Filters /><List />はどちらも親コンポーネントの子です。
  3. <Filters /><List />は完全に別々のルートコンポーネントにあります。

私が考えていない他のシナリオがあるかもしれません。あなたのものがこれらに収まらない場合は、私に知らせてください。これが私が最初の2つのシナリオをどのように扱ってきたかの非常に大まかな例です。

シナリオ1

ハンドラーを<List />から<Filters />に渡すことができます。そして、ハンドラーをonChangeイベントで呼び出して、現在の値でリストをフィルター処理できます。

#1のJSFiddle→

/** @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);

シナリオ2

シナリオ1と似ていますが、親コンポーネントはハンドラー関数を<Filters />に渡し、フィルタリングされたリストを<List />に渡します。 <List /><Filters />から切り離すので、私はこの方法が好きです。

#2のJSFiddle→

/** @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);

シナリオ3

コンポーネントがいかなる種類の親子関係の間でも通信できないときは、 ドキュメントではグローバルイベントシステム を設定することを推奨しています。

305
Michael LaCroix

コンポーネントを通信させる方法は複数あります。あなたのユースケースに合うものもあります。これは私が知っていると便利だと思ったいくつかのリストです。

反応する

親子直接コミュニケーション

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

ここで、子コンポーネントは、親によって提供されたコールバックを値で呼び出し、親は親の子によって提供された値を取得できます。

アプリの機能/ページを作成する場合は、コールバック/状態を管理する単一の親(containerまたはsmart componentとも呼ばれます)を持ち、すべての子をステートレスにし、親にのみレポートするようにします。このようにして、あなたはそれを必要とするあらゆる子供に簡単に親の状態を「共有」することができます。


コンテキスト

React Contextは、コンポーネント階層のルートで状態を保持し、この状態を非常に深くネストされたコンポーネントに簡単に挿入できるようにします。これにより、すべての中間コンポーネントにプロップを渡す必要がなくなります。

今までコンテキストは実験的な機能でしたが、新しいAPIは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>
);

消費者は レンダープロップ/子関数パターンを使用しています

詳細については、この ブログ投稿 を確認してください。

React 16.3より前は、非常によく似たAPIを提供する react-broadcast を使用することをお勧めします。そして、以前のコンテキストAPIを使用します。


ポータル

通常の親子のように、2つのコンポーネントを単純な関数と通信させるために2つのコンポーネントを密接に結び付けたい場合は、ポータルを使用します。それが意味する視覚的/ CSS制約の(z-index、opacityなどのように)。

この場合は「ポータル」を使用できます。 ポータル を使用したさまざまな反応ライブラリがあり、通常は モーダル 、ポップアップ、ツールチップなどに使用されます。

次の点を考慮してください。

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

reactAppContainer内にレンダリングすると、次のDOMを生成する可能性があります。

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

詳細はこちら


スロット

どこかにスロットを定義してから、レンダーツリーの別の場所からそのスロットを埋めます。

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>

これはポータルと少し似ていますが、塗りつぶされたコンテンツは定義したスロットにレンダリングされますが、ポータルは通常新しいDOMノード(多くの場合document.bodyの子)をレンダリングします。

react-slot-fill ライブラリをチェックする


イベントバス

React ドキュメント で述べられているように、

親子関係を持たない2つのコンポーネント間の通信のために、あなたはあなた自身のグローバルイベントシステムを設定することができます。 componentDidMount()でイベントを購読し、componentWillUnmount()で購読を解除し、イベントを受信したらsetState()を呼び出します。

イベントバスを設定するために使えることはたくさんあります。リスナーの配列を作成するだけで、イベントの発行時にすべてのリスナーがイベントを受け取ります。あるいは、 EventEmitterPostalJs のようなものを使うこともできます。


フラックス

Flux は基本的にイベントバスですが、イベントレシーバはストアです。これは基本的なイベントバスシステムと似ていますが、状態がReactの外部で管理されている点が異なります。

オリジナルのFluxの実装は、イベントソーシングをハックな方法でやろうとする試みのように見えます。

Redux は私にとってはイベントソーシングから最も近いFluxの実装です。タイムソーシングのようなイベントソーシングの利点の多くには利点があります。 Reactと厳密にはリンクされておらず、他の機能ビューライブラリと一緒に使用することもできます。

EggheadのRedux ビデオチュートリアル は本当に素晴らしいもので、内部でどのように機能するかを説明しています(本当に簡単です)。


カーソル

カーソルは ClojureScript/Om から来ており、Reactプロジェクトで広く使われています。それらはReactの外で状態を管理することを許し、そしてコンポーネントツリーについて何も知る必要なしに、複数のコンポーネントが状態の同じ部分への読み書きアクセスを持つことを可能にします。

ImmutableJSReact-cursorOmniscient など、多くの実装が存在します。

Edit 2016:カーソルが小さいアプリケーションではうまく機能することに人々は同意しているようですが、複雑なアプリケーションではうまくスケーリングできません。 Om Nextにはもうカーソルがありません(最初にこの概念が導入されたのはOmです)。


エルム建築

Elmアーキテクチャ は、 Elm言語 によって使用されることが提案されているアーキテクチャです。 ElmがReactJSでなくても、ElmアーキテクチャはReactでも実行できます。

Reduxの作者であるDan Abramovは、Reactを使ってElmアーキテクチャの 実装 を行いました。

ReduxとElmはどちらも本当に素晴らしく、フロントエンドでイベントソーシングの概念を強化する傾向があります。どちらもタイムトラベルデバッグ、元に戻す/やり直し、再生を可能にします...

ReduxとElmの主な違いは、Elmは状態管理に関してはるかに厳密になる傾向があるということです。 Elmでは、ローカルコンポーネントの状態やマウント/アンマウントフックを持つことはできず、すべてのDOMの変更はグローバルな状態の変更によって引き起こされる必要があります。 Elmアーキテクチャーは、単一の不変オブジェクト内の状態をALL処理することを許可するスケーラブルなアプローチを提案しています。一方、Reduxはを処理するように招待するアプローチを提案します単一の不変オブジェクト内の状態の_ most _

Elmの概念モデルは非常に洗練されており、アーキテクチャは大規模なアプリケーションでも十分に拡張できますが、マウント後に入力にフォーカスをあてる、既存のライブラリと統合するといった単純なタスクを達成するのは実際には難しいか、より定型的命令型インタフェース(JQueryプラグイン)を使って。 関連する問題

また、Elmアーキテクチャーは、より多くのコード定型句を必要とします。それほど冗長でも複雑でもありませんが、Elmアーキテクチャーは静的型付け言語に適していると私は思います。


FRP

RxJS、BaconJS、Kefirなどのライブラリを使用して、コンポーネント間の通信を処理するためのFRPストリームを生成できます。

あなたは例えば試すことができます Rx-React

私はこれらのライブラリを使うことはElm言語が シグナルで提供するものを使うこと と非常に似ていると思う。

CycleJS フレームワークはReactJSを使いませんが、 vdom を使います。それはElmアーキテクチャと多くの類似点を共有しています(しかしそれはvdomフックを可能にするので実際に使うのがより簡単です)そしてそれは関数の代わりに広範囲にRxJを使います。反応します。 CycleJs Eggheadのビデオ は、それがどのように機能するのか理解するのにうってつけです。


CSP

CSP(Communicating Sequential Processes)は現在普及しています(主にGo/goroutinesとcore.async/ClojureScriptが原因です)が、 JS-CSP と一緒にJavaScriptでも使用できます。

James Longは、Reactでどのように使用できるかを説明した ビデオ を作成しました。

サガ

サガはDDD/EventSourcing/CQRSの世界から来るバックエンドの概念であり、「プロセスマネージャ」とも呼ばれます。それは redux-saga プロジェクトによって、主に副作用(すなわちAPI呼び出しなど)を扱うためのredux-thunkの代わりとして普及しています。ほとんどの人は現在それが副作用のためだけに役立つと考えていますが、それは実際にはコンポーネントの分離についての詳細です。

サガは最後にFluxアクションを発行するので、それは全く新しい通信システムよりもFluxアーキテクチャ(またはRedux)への賛辞です。あなたがwidget1とwidget2を持っていて、それらを切り離したいのであれば、widget1からwidget2をターゲットとするアクションを起動することはできません。そのため、widget1は自分自身をターゲットとするアクションのみを起動し、sagaはwidget1アクションをリッスンする「バックグラウンドプロセス」であり、widget2をターゲットとするアクションをディスパッチすることができます。佐賀は2つのウィジェット間の結合点ですが、ウィジェットは分離されたままです。

あなたが興味を持っているならば、 私の答えをここで見てください


結論

これらの異なるスタイルを使用した同じ小さなアプリケーションの例を見たい場合は、この リポジトリ のブランチを確認してください。

私は長期的に最良の選択肢が何であるかわかりませんが、Fluxがイベントソーシングのように見えるのが本当に好きです。

イベントソーシングの概念がわからない場合は、この非常に教育的なブログをご覧ください。 Apache Samza でデータベースを裏返しにしているので、Fluxがなぜ必要なのか理解するのは必読です。いいですね(でもこれはFRPにも当てはまります)

私は、最も有望なFluxの実装は Redux であるとコミュニティは同意すると思います。印象的なライブコーディングala Bret Victorの Principle on video のインベントリ作成が可能です。

147

わかりました、それにはいくつかの方法がありますが、Reduxを使用してストアを使用することに専念したいと思います。純粋なReactを使用すると実際の大きなアプリケーションで混乱してしまいコンポーネント間の通信がアプリケーションとして難しくなるため、このような状況では単純な解決策を提示するのではなく、純粋なReactを使用してください。成長する...

では、Reduxはあなたにとって何をするのでしょうか。

Reduxは、アプリケーション内のローカルストレージのようなもので、アプリケーションのさまざまな場所でデータを使用する必要があるときにいつでも使用できます。

基本的に、Reduxのアイデアは元々フラックスから来ていますが、ただ1つのストアを作成することによって1つの真実の源を持つというコンセプトを含む、いくつかの基本的な変更があります...

下のグラフを見て、FluxReduxの違いを確認してください。

Redux and Flux

アプリケーションがコンポーネント間の通信を必要とする場合は、最初からReduxを適用することを検討してください。

また、Redux Documentationからこれらの単語を読むことは有用であるかもしれません:

JavaScriptシングルページアプリケーションの要件がますます複雑になっているので、私たちのコードはこれまで以上に多くの状態を管理しなければなりません。この状態には、サーバーの応答とキャッシュされたデータ、およびサーバーにまだ永続化されていないローカルで作成されたデータが含まれることがあります。アクティブなルート、選択したタブ、スピナー、ページネーションコントロールなどを管理する必要があるため、UIの状態も複雑さを増しています。

この絶え間なく変化する状態を管理することは困難です。モデルが別のモデルを更新できる場合、ビューはモデルを更新し、それによって別のモデルが更新されます。これにより、別のビューが更新される可能性があります。ある時点で、いつ、なぜ、そしてどのようにその状態を制御することができなくなったので、あなたはもはやあなたのアプリで何が起こるのか理解していません。システムが不透明で不確定な場合、バグを再現したり新しい機能を追加したりするのは困難です。

これで十分ではないかのように、フロントエンド製品開発で新しい要件が一般的になることを検討してください。開発者として、私たちは楽観的な更新、サーバーサイドのレンダリング、ルート遷移を実行する前のデータの取得などを処理することが期待されています。私たちは、これまで対処したことのない複雑さを管理しようとしていることに気づき、必然的に質問をします。答えはノーだ。

この複雑さは、私たちが考えることが難しい2つの概念、すなわち突然変異と非同期性を混在させているため、処理が困難です。私はそれらをMentosとCokeと呼んでいます。どちらも離れ離れにはなり得ますが、一緒になると混乱が生じます。 Reactのようなライブラリは、非同期と直接DOM操作の両方を削除することによって、ビューレイヤでこの問題を解決しようとします。ただし、データの状態を管理するのはあなた次第です。これはReduxが入るところです。

Flux、CQRS、およびEvent Sourcingのステップに従って、Reduxは、更新が行われる方法とタイミングに特定の制限を課すことによって状態の変化を予測可能にしようとします。 これらの制限はReduxの3つの原則に反映されています。

5
Alireza

これが私がこれを処理した方法です。
月の<select>の日の<select>があるとしましょう日数は選択した月によって異なります。

両方のリストは3番目のオブジェクト、左パネルによって所有されています。両方の<select>はleftPanel <div>の子でもあります
LeftPanelコンポーネントにコールバックとハンドラを持つゲームです。

テストするには、コードを2つの別々のファイルにコピーしてindex.htmlを実行するだけです。次に月を選択して、日数がどのように変わるかを確認します。

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

そして左パネルコンポーネントを実行するためのHTMLindex.html

<!DOCTYPE html>
<html>
<head>
    <title>Dates</title>

    <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.6.0/underscore-min.js"></script>
    <script src="//fb.me/react-0.11.1.js"></script>
    <script src="//fb.me/JSXTransformer-0.11.1.js"></script>
</head>

    <style>

        #dayPicker {
            position: relative;
            top: 97px;
            left: 20px;
            width: 60px;
            height: 17px;
        }

        #monthPicker {
            position: relative;
            top: 97px;
            left: 22px;
            width: 95px;
            height: 17px;
        }

        select {
            font-size: 11px;
        }

    </style>


    <body>
        <div id="leftPanel">
        </div>

        <script type="text/jsx" src="dates.js"></script>

    </body>
</html>
5
Skulas

私はその質問にはすでに答えているのを見ましたが、もっと詳しく知りたいのであれば、コンポーネント間の通信は全部で3つのケースがあります

  • ケース1:親から子へのコミュニケーション
  • ケース2:子供から親へのコミュニケーション
  • ケース3:無関係な構成要素(任意の構成要素から任意の構成要素へ)のコミュニケーション
3
Kaloyan Kosev

コンポーネントがあらゆる種類の親子関係の間で通信することができないというシナリオである場合の@MichaelLaCroixの回答を拡張するため、この文書ではグローバルイベントシステムを設定することをお勧めします。

<Filters /><TopBar />が上記の関係を持たない場合は、単純なグローバルエミッタを次のように使用します。

componentDidMount - イベントを購読する

componentWillUnmount - イベントの購読を中止

React.jsとEventSystemのコード

EventSystem.js

class EventSystem{

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

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

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

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

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

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

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

        return true;
    }

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

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

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

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

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

        return [namespace, callback];
    }

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

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

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

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

}

NotificationComponent.js

class NotificationComponent extends React.Component {

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

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

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

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

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

    render() {

    }
}
1
tsuz

次のコードは私が2人の兄弟の間のコミュニケーションをセットアップするのを助けます。設定は、render()およびcomponentDidMount()の呼び出し中にその親で行われます。それはに基づいています https://reactjs.org/docs/refs-and-the-dom.html それが役立つことを願っています。

class App extends React.Component<IAppProps, IAppState> {
    private _navigationPanel: NavigationPanel;
    private _mapPanel: MapPanel;

    constructor() {
        super();
        this.state = {};
    }

    // `componentDidMount()` is called by ReactJS after `render()`
    componentDidMount() {
        // Pass _mapPanel to _navigationPanel
        // It will allow _navigationPanel to call _mapPanel directly
        this._navigationPanel.setMapPanel(this._mapPanel);
    }

    render() {
        return (
            <div id="appDiv" style={divStyle}>
                // `ref=` helps to get reference to a child during rendering
                <NavigationPanel ref={(child) => { this._navigationPanel = child; }} />
                <MapPanel ref={(child) => { this._mapPanel = child; }} />
            </div>
        );
    }
}
0
Sergei Zinovyev

誰もmobxを言及していません。アイデアはreduxに似ています。複数のコンポーネントが購読しているデータがある場合は、このデータを使用して複数のコンポーネントを駆動できます。

0
windmaomao

コンポーネント間のコミュニケーションの選択肢を探求し、それがどんどん難しくなっているように感じる場合は、良いデザインパターンを採用することを検討してください。 Flux

これは、アプリケーション全体の状態をどのように格納および変更し、その状態を使用してコンポーネントをレンダリングするかを定義した単なる規則の集まりです。

Fluxの実装はたくさんありますが、 Facebookの公式実装 もそのうちの1つです。もっとも定型的なコードが含まれていると考えられていますが、ほとんどのものは明示的であるため理解しやすいです。

その他の選択肢のいくつかは、 flummoxfluxxor流暢 および redux

0
Kemal Dağ

私はかつてあなたが今いる場所でしたが、初心者として、あなたはこれをどのように反応させるかについて時々場違いを感じます。今私が考えているのと同じように取り組むつもりです。

状態はコミュニケーションの基礎です

通常は、3つのコンポーネントを指摘する場合にこのコンポーネントの状態を変更する方法です。

<List />:おそらくフィルターに応じてアイテムのリストを表示します<Filters />:データを変更するフィルターオプション。 <TopBar />:オプションのリスト。

この相互作用をすべて調整するには、より高いコンポーネントが必要になります。これをアプリと呼びます。これは、アクションとデータを各コンポーネントに渡し、たとえば次のようになります。

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

したがって、setFilterが呼び出されると、filteredItemに影響し、両方のコンポーネントを再レンダリングします。これが完全に明確でない場合は、単一のファイルをチェックインできるチェックボックス付きの例を作成しました。

import React, {Component} from 'react';
import {render} from 'react-dom';

const Person  = ({person, setForDelete}) => (
          <div>
            <input type="checkbox" name="person" checked={person.checked} onChange={setForDelete.bind(this, person)} />
            {person.name}
          </div>
);


class PeopleList extends Component {

  render() {

    return(
      <div>
       {this.props.people.map((person, i) => {
         return <Person key={i} person={person} setForDelete={this.props.setForDelete} />;
       })}
       <div onClick={this.props.deleteRecords}>Delete Selected Records</div>
     </div>
    );
  }

} // end class

class App extends React.Component {

  constructor(props) {
    super(props)
    this.state = {people:[{id:1, name:'Cesar', checked:false},{id:2, name:'Jose', checked:false},{id:3, name:'Marbel', checked:false}]}
  }

  deleteRecords() {
    const people = this.state.people.filter(p => !p.checked);

    this.setState({people});
 }

  setForDelete(person) {
    const checked = !person.checked;
    const people = this.state.people.map((p)=>{
      if(p.id === person.id)
        return {name:person.name, checked};
      return p;
    });

    this.setState({people});
  }

  render () {

    return <PeopleList people={this.state.people} deleteRecords={this.deleteRecords.bind(this)} setForDelete={this.setForDelete.bind(this)}/>;
  }
}

render(<App/>, document.getElementById('app'));
0
cabolanoz

たとえ彼らが親子関係でなくてもそのような可能性があります、そしてそれはフラックスです。 (個人的には)Alt.JS(Alt-Containerあり)という実装にはかなり良い実装があります。

たとえば、コンポーネントの詳細に設定されている内容に依存するサイドバーを作成できます。コンポーネントのサイドバーはSidebarActionsとSidebarStoreに接続されていますが、DetailsはDetailsActionsとDetailsS​​toreです。

あなたはそのようにAltContainerを使用することができます

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

{this.props.content}

これは店を維持するでしょう(まあ私は「店」小道具の代わりに「店」を使用することができました)。さて、{this.props.content}はルートによっては詳細になります。/Detailsはそのビューにリダイレクトします。詳細には、たとえばサイドバー要素がチェックされている場合はXからYに変更するチェックボックスがあります。

技術的にはそれらの間に関係はありません、そしてそれは流束なしでするのは難しいでしょう。しかしそれはかなり簡単です。

それではDetailsActionsに行きましょう。そこに作ります

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

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

と詳細ストア

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

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

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

そして今、これは魔法が始まる場所です。

ご覧のとおり、SidebarActions.ComponentStatusChangedにbindListenerがあります。これはsetSiteComponentが使用される場合に使用されます。

今すぐSidebarActionsに

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

こんなものがあります。それは呼び出し時にそのオブジェクトをディスパッチします。そしてそれはstore内のsetSiteComponentが使用される場合に呼び出されます(あなたは例えばボタンのonChangeの間にコンポーネントで使用できます)

今SidebarStoreで我々は持っているでしょう

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

これでDetailsS​​toreを待つことがわかります。どういう意味ですか?多かれ少なかれ、このメソッドは自分自身を更新する前にDetailsS​​toretoの更新を待つ必要があります。

tl; dr One Storeはストア内のメソッドをリッスンしていて、コンポーネントアクションからアクションをトリガーし、それによって独自のストアが更新されます。

私はそれがどういうわけかあなたを助けることができると思います。

0
Shiroo