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


321

私はReactJSを使い始めたばかりで、私が抱えている問題に少し行き詰まっています。

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

これらの3つのコンポーネントを相互に作用させるにはどうすればよいですか、または変更を加えることができるある種のグローバルデータモデルが必要ですか?


3つの兄弟コンポーネントはすべてですか、それとも他のコンポーネントの1つですか?
pgreen2 2014年

これらはすべて3つのコンポーネントであり、アプリケーションを再配置して、データを提供できる同じ親を持つようになりました。
woutr_be 2014年

4
ここでは、fluxまたはpubsubパターンを使用できます。react docsのドキュメントに基づいて、「親子関係を持たない2つのコンポーネント間の通信のために、独自のグローバルイベントシステムをセットアップできます。」というややあいまいな文を残しています。facebook.github.io/react/tips/...
BingeBoy

@BingeBoyが正しいFluxは、データフローの問題、多くのコンポーネントによるデータ共有の問題を処理できるreactjsアプリを作成する優れた方法です。
Ankit Patial

4
FluxやReduxを使いたくない場合、これはこのトピックに関する素晴らしい記事ですandrewhfarmer.com/component-communication
garajo

回答:


318

最良のアプローチは、これらのコンポーネントの配置をどのように計画するかによって異なります。現在思い浮かぶいくつかのシナリオ例:

  1. <Filters /> の子コンポーネントです <List />
  2. どちら<Filters /><List />親コンポーネントの子であります
  3. <Filters />そして<List />、完全に独立したルート要素に住んでいます。

私が考えていない他のシナリオがあるかもしれません。あなたのものがこれらに収まらない場合は、私に知らせてください。最初の2つのシナリオを処理する方法の大まかな例を次に示します。

シナリオ#1

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

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

シナリオ#2

シナリオ#1と同様ですが、親コンポーネントはハンドラー関数を<Filters />に渡し、フィルターされたリストをに渡し<List />ます。このメソッドは、を<List />から切り離すので、より良い方法です<Filters />

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

シナリオ#3

コンポーネントが何らかの親子関係の間で通信できない場合、ドキュメントではグローバルイベントシステムのセットアップを推奨しています


6
機能:#2との良いところは、彼らが唯一の各コンポーネントに小道具を渡す親に依存しているということであるupdateFilter<Filters />し、として配列itemsします<List />。これらの子コンポーネントを、一緒にまたは単独で、動作が異なる他の親で使用できます。たとえば、動的なリストを表示したいがフィルタリングは必要なかった場合などです。
Michael LaCroix、2014年

2
@woutr_beそれが要件に適合するかどうかはわかりませんが、同じような状況に戻ったときに、次の2つの関数を使用して子コンポーネントと親コンポーネント間の通信をソートしました:-listenTo:function(eventName、eventCallback){$( window.document).bind(eventName、eventCallback);} triggerEvent:function(eventName、params){$ .event.trigger(eventName、params);}よろしくお願いします!(申し訳ありませんが、フォーマットを改善できませんでした)
5122014009 2014年

29
シナリオ3の場合、推奨されるアプローチはありますか?カスタム合成イベントを生成することによるこれに関するドキュメントまたは例はありますか?メインのドキュメントには何も見つかりませんでした。
pwray 2014年

1
シナリオ#2 非常に理にかなっています... デザイン(レイアウトの場合のみ)を危険にさらす必要があるまで -次に、EventHub / PubSubの必要性を認識します。
コーディ、

4
シナリオ3のリンクは無効になり、無関係なReact docsページにリダイレクトされます。
ベポーター、

170

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

反応する

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

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

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

アプリの機能/ページを構築する場合、それは(とも呼ばれるコールバック/状態を管理する単一の親を持っている方が良いでしょうcontainersmart component)、およびすべてのチャイルズはステートレスであるためには、唯一の親に事を報告します。このようにして、親の状態を、それを必要とするすべての子と簡単に「共有」できます。


環境

React Contextは、コンポーネント階層のルートに状態を保持することを許可し、非常に深くネストされたコンポーネントにこの状態を簡単に注入できるため、支柱をすべての中間コンポーネントに渡す必要がありません。

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

コンシューマーはレンダープロップ/子関数パターンを使用しています

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

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


ポータル

2つのコンポーネントを近づけて通常の親/子のように単純な機能と通信したいが、これら2つのコンポーネントがDOMで親/子関係を持たないようにしたい場合は、ポータルを使用します。それが意味する視覚的/ CSS制約(z-index、不透明度など)。

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

以下を検討してください。

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

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

<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のドキュメントに記載されているように:

親子関係のない2つのコンポーネント間の通信では、独自のグローバルイベントシステムをセットアップできます。イベントを受け取ったら、componentDidMount()でイベントをサブスクライブし、componentWillUnmount()でサブスクライブを解除し、setState()を呼び出します。

イベントバスのセットアップに使用できるものはたくさんあります。リスナーの配列を作成するだけで、イベントの発行時にすべてのリスナーがイベントを受け取ります。または、EventEmitterPostalJsなどを使用できます。


フラックス

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

元のFlux実装は、ハックな方法でイベントソーシングを行う試みのように見えます。

Reduxは、私にとって、イベントソーシングから最も近いFlux実装です。これは、時間旅行などのイベントソーシングの利点の多くにメリットをもたらします。Reactに厳密にリンクされているわけではなく、他の機能ビューライブラリでも使用できます。

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


カーソル

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

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

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


Elmアーキテクチャ

エルム・アーキテクチャは、で使用することが提案アーキテクチャであるエルム言語。ElmがReactJSでない場合でも、ElmアーキテクチャーはReactでも実行できます。

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

ReduxとElmはどちらも非常に優れており、フロントエンドでイベントソーシングの概念を強化する傾向があり、どちらも時間旅行のデバッグ、元に戻す/やり直し、再生を可能にします...

ReduxとElmの主な違いは、Elmは状態管理についてより厳格になる傾向があることです。Elmでは、ローカルコンポーネントの状態またはマウント/アンマウントフックを使用できません。すべてのDOMの変更は、グローバルな状態の変更によってトリガーされる必要があります。エルムアーキテクチャは、処理するために許可するスケーラブルなアプローチを提案ALL Reduxのは、招待はあなたが処理することのアプローチを提案しながら、単一不変オブジェクトの内部状態をMOST単一不変オブジェクトで状態のを。

Elmの概念モデルは非常に洗練されており、アーキテクチャは大規模なアプリでも適切にスケーリングできますが、実際には、実装後に入力にフォーカスを当てる、または既存のライブラリと統合するなどの単純なタスクを達成するのが難しいか、より多くのボイラープレートが必要になる場合があります命令型インターフェース(つまり、JQueryプラグイン)。関連する問題

また、Elmアーキテクチャには、より多くのコードボイラープレートが含まれます。書くのはそれほど冗長でも複雑でもありませんが、Elmアーキテクチャーは静的型付け言語により適していると思います。


FRP

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

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

これらのライブラリを使用することは、ELM言語が提供するシグナルを使用することと非常に似ていると思います。

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


CSP

CSP(Communicating Sequential Processes)は現在人気がありますが(主にGo / goroutinesとcore.async / ClojureScriptが原因です)、JavaScriptとJS-CSPを併用することもできます。

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

サガ

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

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

興味があればここ私の答えを見てください


結論

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

長期的にはどのオプションが最適かわからないが、Fluxがイベントソーシングのように見えるのが本当に好きだ。

イベントソーシングの概念がわからない場合は、この非常に教育的なブログをご覧ください。ApacheSamzaを使用してデータベースを裏返しにすることは、Fluxが優れている理由を理解するための必読です(ただし、これはFRPにも当てはまる可能性があります) )

コミュニティは、最も有望なFlux実装がReduxであることに同意していると思います。Reduxは、ホットリロードにより、非常に生産的な開発者エクスペリエンスを徐々に可能にします。印象的なライブコーディングala Bret Victorの原理に関する発明のビデオが可能です!


2
すばらしい答えSeb!
Ali Gajani

7

OK、それを行う方法はいくつかありますが、私は専らReduxを使用してストアを使用することに焦点を当てたいので、この場合にのみ迅速な解決策を提供するのではなく、これらの状況であなたの人生をはるかに容易にします。実際の大きなアプリケーションとコンポーネント間の通信は、アプリケーションが成長するにつれてますます難しくなります...

それで、Reduxはあなたのために何をしますか?

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

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

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

ReduxとFlux

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

Reduxドキュメンテーションからこれらの単語を読むことも、最初に役立つかもしれません:

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

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

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

この複雑さは、人間の心が考えるのが非常に難しい2つの概念、つまり突然変異と非同期性を混合しているため、処理するのが困難です。私はそれらをメントスとコーラと呼んでいます。どちらも分離に優れていますが、一緒にすると混乱を引き起こします。Reactのようなライブラリは、非同期と直接DOM操作の両方を削除することにより、ビューレイヤーでこの問題を解決しようとします。ただし、データの状態の管理はユーザーに任されています。ここからReduxが登場します。

段階で、次のフラックス、CQRS、およびイベントソーシング更新が発生する可能性がどのように、いつに一定の制限を課すことによって、状態変異が予測可能にするReduxの試み。これらの制限は、Reduxの3つの原則に反映されています。


reduxはどのように役立ちますか?datepicker(コンポーネントとして)のモーダルがあり、そのコンポーネントを同じページにある複数のコンポーネントからロードできる場合、datepickerコンポーネントはどのアクションをreduxにディスパッチするかをどのようにして知るのでしょうか?これが問題の本質であり、あるコンポーネントのアクションを別のコンポーネントではなく他のコンポーネントにリンクします。(datepickerそれ自体がモーダルコンポーネント自体の中で深い、深いコンポーネントであることを考慮してください)
vsync

@vsyncはreudxを単一の静的な値とは見なしません。reduxは実際にはオブジェクト、配列を持つことができるので、オブジェクトまたは配列として、またはストアに何でも保存できます。mapdispatchtopropsで、それぞれをオブジェクト配列に保存できます。例:[{name: "picker1"、value: "01/01/1970"}、{name: "picker2"、value: "01/01/1980"}]そして、親でmapstatetopropsを使用して、各コンポーネントまたは必要な場所、それがあなたの質問に答えるかどうかはわかりませんが、コードが表示されません...それらが別々のグループにある場合は、より詳細に反対することもできます...しかし、どのようにグループ化するかはすべて次第ですそれら..
Alireza

質問はに関するものではありませんreduxし、何を保存することができますが、どのようにそれをトリガーするために必要なものに深いダウンアクションを渡します。ディープコンポーネントは、トリガーする必要があるものをどのようにして知るのですか?この例では、シナリオに応じて特定のレデューサーをトリガーする一般的なコンポーネントを指定しているため、どのコンポーネントにも日付ピッカーモーダルを使用できるため、異なるレデューサーになる可能性があります。
vsync

5

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

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

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

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

そして、左パネルコンポーネントindex.htmlを実行するための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>

サンプルコードの80%を削除してもポイントを維持できるのであれば、それが最善です。このスレッドのコンテキストでCSSを表示することは無関係
vsync

3

質問にはすでに回答済みですが、詳細を知りたい場合は、コンポーネント間の通信が合計3ケースあります

  • ケース1:親から子へのコミュニケーション
  • 事例2:子どもから親へのコミュニケーション
  • ケース3:関連しないコンポーネント(任意のコンポーネントから任意のコンポーネント)の通信

1

コンポーネントがあらゆる種類の親子関係の間で通信できないというシナリオである場合、@ 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() {

    }
}

0

親子関係でなくても可能性はあります、それがフラックスです。Alt.JS(Alt-Containerを使用)と呼ばれる実装には(個人的には)かなり良いものがあります。

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

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

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

{this.props.content}

これは店舗を維持します(「stores」プロップの代わりに「store」を使用できます)。現在、{this.props.content}はルートに応じて詳細にできます。/ Detailsがそのビューにリダイレクトするとします。詳細には、たとえば、チェックされるとサイドバー要素をXからYに変更するチェックボックスがあります。

技術的にはそれらの間に関係はなく、フラックスなしで行うのは難しいでしょう。しかし、それはかなり簡単です。

次に、DetailsActionsに移動します。そこに作成します

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

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

and DetailsS​​tore

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

そんなものがあります。呼び出し時にそのオブジェクトをディスパッチします。そして、ストア内のsetSiteComponentが使用される場合に呼び出されます(たとえば、Buttonの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;
}

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

tl; dr 1つのストアはストア内のメソッドをリッスンしており、独自のストアを更新するコンポーネントアクションからアクションをトリガーします。

なんとかお役に立てれば幸いです。


0

コンポーネント間の通信のオプションを調べて、それがますます難しくなっているように感じたい場合は、良い設計パターンであるFluxを採用することを検討してください。

これは単に、アプリケーション全体の状態を保存および変更し、その状態を使用してコンポーネントをレンダリングする方法を定義するルールのコレクションです。

Fluxの実装は数多くあり、Facebookの公式実装もその1つです。ほとんどのボイラープレートコードを含むものと見なされますが、ほとんどのことは明示的であるため、理解しやすくなっています。

他の選択肢のいくつかはflummox fluxxor fluxible再来


0

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

国家はコミュニケーションの要である

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

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

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

<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

次のコードは、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>
        );
    }
}

これはTypeScriptです。おそらく回答で言及する必要があります。良いコンセプトですが。
serraosays

0

奇妙なことに誰も言及しなかったmobx。アイデアはに似ていreduxます。複数のコンポーネントがサブスクライブしているデータがある場合、このデータを使用して複数のコンポーネントを駆動できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.