335
273

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Flux を使わずに React コンポーネント間のコミュニケーションを行う8つの方法

Last updated at Posted at 2016-12-31

注意

これは 8 no-Flux strategies for React component communication を意訳し、自分の解釈や補足を追加した記事になります。

実際に自分が React でコードを書いていて、疑問に思い調べた際に参考にしました(結局は Flux/Redux と 方法1. Props を使ったのですが)。

はじめに

React で、最初の大きな問題の1つは「コンポーネントはどうやって他のコンポーネントとコミュニケーションするべきか」という事です。

コンポーネント2がクリックされた事をコンポーネント1に伝える最も良い方法は何でしょうか?

これには、とてもたくさんの答えがあります。早かれ遅かれ、Flux が言及されるでしょう。

Flux アーキテクチャでコミュニケーション問題を解決できるでしょうか?Flux は必要でしょうか?

この問題は Flux を使用することによって解決できる事がありますが、必ずしも必要ではありません。私のリストのどの方法でも Flux は使用していません。

開発者の中には、Flux について考えることが難しい人もいます。他にも、彼らのアプリにうまく合わないと感じている人もいます。開発者はもっと簡単な方法がある事を分かっていないまま、その人気から Flux を使います。

そこでここでは React コンポーネント間のコミュニケーションを行う8つの簡単な方法を紹介します。

8つの方法

どの方法を使用するかは、コミュニケーションする必要のあるコンポーネント間の関係によって決まります。

これを目次として使用して、あなたのユースケースにあてはまる方法を選択することが出来ます。

親から子 子から親 兄弟間 任意間
方法1. Props 方法3. コールバック関数 方法5. 親コンポーネント 方法6. Observer パターン
方法2. Ref 関数 方法4. イベントバブリング 方法7. グローバル変数
方法8. Context

方法1. Props

Props はコンポーネント間でデータを通信する最も一般的な方法です。Props を使用すると、親コンポーネントから子コンポーネントへとデータを送信できます。

Props は React の主要な機能で、あなたはまったくこれを使わずに React を使うことは出来ません。そのため、もしあなたがまだ props についてよく分かっていないのであればドキュメントにもどる時間です。Multiple Components のページから始めてください。

補足

この方法は親から直接の子であれば簡単なのですが、子の子や、子の子の子など階層が深くなると、親から最終的に Props を利用する子へ Props のバケツリレーが必要になってしまいます。途中のすべてのコンポーネントはただ Props を中継するためにその中継処理を書く必要があります。

これを避けるために JSX Spread Attributes という方法があります。

ただ、これだと中継しているコンポーネントにその親コンポーネントの Props がすべてアクセスできてしまう副作用もあります。

他の方法か、そもそも Flux の採用を検討してもよさそうです。

また、「Multiple Components」のリンク先が「Composition vs Inheritance」に変わっていましたが、そのままにしました。

方法2. Ref 関数

Ref 関数は親コンポーネントから子コンポーネントにコミュニケーションする別の方法です。

子コンポーネントでメソッドを書いて、親コンポーネントからそのメソッドを this.refs.<ref name>.<function name> のようにして呼び出します。簡単な例は次になります。

class TheChild extends React.Component {
  myFunc() {
    return 'hello';
  }
}
class TheParent extends React.Component {
  render() {
    return (
      <TheChild ref='foo' />
    );
  }
 
  componentDidMount() {
    var x = this.refs.foo.myFunc();
    // x は 'hello' 
  }
}

方法3. コールバック関数

方法1と2は、親から子へデータの受け渡しを行うものでしたが、その逆はどうすればよいでしょうか?子から親にどのようにデータを送るのでしょうか?

最も簡単な方法は親が子に関数を渡すことです。子はその関数を使って親と通信することができます。

親は子に関数をプロパティとして、次のように渡します。

<MyChild myFunc={this.handleChildFunc.bind(this)} />

子はその関数を次のようにして呼び出します。

this.props.myFunc();

また、子ではこの関数の PropTypes 宣言を忘れないようにしてください。

MyChild.propTypes = {
  myFunc: React.PropTypes.func,
};

方法4. イベントバブリング

イベントバブリングは React の概念ではなく、古い DOM の概念になります。コールバック関数のようなもので、子コンポーネントから親コンポーネントへとデータを送る方法になります。

これは親コンポーネントが子コンポーネントの DOM 要素で発生した DOM イベントを取得したい場合に機能します。

次に、すべての子コンポーネントで onkeyup イベントをキャッチする親コンポーネントの例を示します。

class ParentComponent extends React.Component {
  render() {
    return (
      <div onKeyUp={this.handleKeyUp.bind(this)}>
        // 任意の数の子コンポーネントをここに追加できます。
      </div>
    );
  }
 
  handleKeyUp(event) {
    // この関数はすべての子コンポーネントによって
    // レンダリングされる <input /> の 'onkeyup'
    // イベントから呼び出されます。
  }
}

補足

バブリングを停止したい時はおなじみの e.stopPropagation() を使います。

イベントハンドラから false を返して停止する方法は v0.14 以降できなくなっています。

方法5. 親コンポーネント

2つのコンポーネントが通信する必要がある場合、それらは兄弟でなければならないサインかもしれません。

親コンポーネントに複数のコンポーネントをラップする場合、上記の方法のいくつかを組み合わせて使用することで、そららの通信を簡単にすることができます。

class ParentComponent extends React.Component {
  render() {
    return (
      <div>
        <SiblingA
          myProp={this.state.propA}
          myFunc={this.siblingAFunc.bind(this)}
          />
        <SiblingB
          myProp={this.state.propB}
          myFunc={this.siblingBFunc.bind(this)}
          />
      </div>
    );
  }
  // 'siblingAFunc' と 'siblingBFunc' はここで定義する
}

方法6. Observer パターン

Overser パターンはオブジェクトが複数の他のオブジェクトにメッセージを送信することの出来る、ソフトウェアデザインパターンです。

この方法を使うには、兄弟関係や親子関係は必要ありません。

React のコンテキスト内では、これは特定のメッセージを受信するために一部のコンポーネントを購読し、他のコンポーネントがそれらのサブスクライバにメッセージをパブリッシュすることを意味します。

コンポーネントは通常、componentDidMount メソッドでサブスクライブし、componentWillUnmount メソッドでアンサブスクライブします。

ここに Observer パターンを実装する4つのライブラリを挙げました。これらの違いはわずかで、 EventEmitter が最も人気があります。

PubSubJS: ”JavaScript で書かれたトピックベースのパブリッシュ/サブスクライブライブラリ”

EventEmitter: ”ブラウザのためにイベントされた JavaScript” 実際には nodejs コアの一部としてすでに存在しますが、ブラウザ用に存在するライブラリの実装です。

MicroEvent.js: ”node とブラウザ用の - 20行しかない - イベントエミッターマイクロライブラリ”

MobX: "オブザーバブルデータ。Reactive 関数。シンプルコード”

補足

感想になりますが、Flux の実装も Observer パターンを使うのではないでしょうか。Flux ではなくこの方法を使うというメリットがイマイチ分かりませんでした。Flux ほど大げさにしたくないという場合に使われるのでしょうか。

方法7. グローバル変数

グローバル変数は、コンポーネント通信用のダクトテープです。宇宙船には使用しないでください 🚀。

個人的なプロジェクトでプロトタイプの場合、何をしてもいいのでしょうか?特にそれがあなたの時間の節約になるのであれば(使ってもよいのではないでしょうか)。

私はグローバル変数の使用を明示的にするという事で window. をオススメします。あるコンポーネントのイベントハンドラまたはライフサイクルメソッドで window.x を設定し、別のコンポーネントで window.x を読みます。

補足

JavaScript に限らずプログラミング一般論として、グローバル変数の使用はお手軽ですが、メンテナンスがしづらくなるという問題があります。

明示的な指定 window. の話とあわせて次のページが参考になります。

方法8. Context

context は props と同様に機能しますが、1つの子にデータを提供するのではなく、サブツリー全体にデータを提供するために使用できます。(子の子、子の子の子、など)。

context はデータをツリー(親から部分木)にだけ送ることが出来ます。コールバック関数とペアにして、データをバックアップする(サブツリーを親に渡す)ことが出来ます。

context は最近まで React のドキュメントにはない機能でした。今では facebook.github.io/react/docs/context.html でドキュメントを見つけることが出来ます。ドキュメントには context のためのいくつかの適切なユースケースがリストされています。

コンテキストの最適な使用例は、ログインしているユーザー、現在の言語、またはテーマ情報を暗黙的に渡す場合です。これらはすべて真のグローバルであるかもしれませんが、context では単一の React サブツリーにスコープできます。

ドキュメントはまた、context の過度な使用を警告しています:

context を使用する必要がある場合は、控えめに使用してください。

補足

ドキュメントを確認したところ、過去のログを含めてここで訳した引用記述を見つけることが出来ませんでした。

ただしドキュメントでは context の使用については ”控えめ” というよりは、「実験的な API なので将来使えなくなる可能性があり、アプリを安定させたい場合には使用しないように」という厳し目の記述でした。

If you want your application to be stable, don't use context. It is an experimental API and it is likely to break in future releases of React.

2017/01/12 追記

引用元ページは http://reactjs.cn/react/docs/context.html でした。はてブでコメントをしていただいた id:koheikimura さん、ありがとうございます。

335
273
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
335
273

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?