Facebook Relayについてまとめ


Reactがもっと広まって欲しいと思っている今日このごろ。React Europeで[Joseph Savona氏の講演](https://www.youtube.com/watch?v=IrgHurBjQbg)で`Relay`についての「モヤっと」がいっきにかなり解消された気がするので、要点を本編を翻訳しながら自分なりにまとめておきます。 **私の理解が誤っている可能性は十二分にありえるので、ご指摘いただければ幸いです。**

# はじめに
ReactとFluxって組み合わせと共によく目にするのが↓の図。
![スクリーンショット 2015-08-14 16.55.39.png](https://qiita-image-store.s3.amazonaws.com/0/38528/bc91c318-02b3-06a1-4b8e-3759955cd362.png "スクリーンショット 2015-08-14 16.55.39.png")

**矢印は一方向にしか進まない**のが特徴で、わかりやすいってのがいろんなところで書かれているんですけど、 **結局データをサーバからとってくるところってどうなってるの？**ってのが疑問として残ります。つまり、図で表現すると↓の部分の仕組みがどうなっているかってところです。

![スクリーンショット 2015-08-14 21.01.33.png](https://qiita-image-store.s3.amazonaws.com/0/38528/86709e0a-91e1-883b-a8ca-b59010266565.png "スクリーンショット 2015-08-14 21.01.33.png")

その部分を、Instagramのようなサービスを例に説明しています。

# クライアントはどのようにしてサーバからデータを取得すべきか
まず、Instagramのようなサービスでフィードを例にとった場合、かなりシンプルに考えた場合でこれは３つのコンポーネント(`Feed`, `Media`, `Comment`)で構成できることがわかります。

![スクリーンショット 2015-08-14 21.03.07.png](https://qiita-image-store.s3.amazonaws.com/0/38528/b5b91fc6-4828-ebd0-53da-8bee38cc7928.png "スクリーンショット 2015-08-14 21.03.07.png")



**たった３つのコンポーネントでInstagramっぽいサービスが作れる** っていうのはやたらと簡単に聞こえるんですけど、この構成でデータってどうやって取ってくるの？ってとこが課題です。 **よくある話**を例にいろんな案を見てみます。

## A案：再利用可能なエンドポイント
なるべく再利用可能なエンドポイントを用意するというやり方で、

* `/feed` 各ユーザのFeedをとってくるエンドポイント
* `/photo/:id` 画像を取得する為のエンドポイント
* `/comment/:id` コメントを取得する為のエンドポイント

のようにエンドポイントを用意するというもの。開発者からすると作りやすい手法なんですけど、サーバへのリクエストも多ければ、エントリが読み込まれる順番が↓のように順不同になるようなUXを招いてしまうこともしばしば。

![スクリーンショット 2015-08-14 21.50.45.png](https://qiita-image-store.s3.amazonaws.com/0/38528/99362f96-afee-acaf-a31e-e0b4cbbd00f4.png "スクリーンショット 2015-08-14 21.50.45.png")

## B案：オールインワンなカスタムエンドポイント
それだったらオールインワンで情報提供してくれるエンドポイント用意したらいいじゃんってことで

`/feed_with_photos_and_comments`

ってのを提供してみます。そうした場合のUXがこちら↓
![スクリーンショット 2015-08-14 21.57.14.png](https://qiita-image-store.s3.amazonaws.com/0/38528/c6110413-a762-da92-0def-b4bae638606e.png "スクリーンショット 2015-08-14 21.57.14.png")

全データ取得するまで`Loading...`が表示されて、取得後にいっきに全部表示されるっていう、今どき考えられないようなUXですね。レスポンシブではない！

## C案：色んなカスタムエンドポイント
最終的によく取られる手法が複数のいろんなカスタムエンドポイント。

* `/feed`というエンドポイントを用意
* `/feed`は`Media Component`に必要な情報を提供
* `/feed`はクエリパラメータで`/feed?count=1&offset=1`のようにどれだけの`Media`情報が必要か指定できる
* ↑の機能があるおかげで、全ての`Media`をいっきに取得するのではなく、別々にAPIを叩いて`Media`を取得可能

この機能を実現することで↓のような **いい感じな**(順に`Media`が読み込まれるような) UXが実現できます。
![スクリーンショット 2015-08-14 22.05.15.png](https://qiita-image-store.s3.amazonaws.com/0/38528/d3c95872-27b3-3f5f-a59a-c603e614d535.png "スクリーンショット 2015-08-14 22.05.15.png")

UXもいい感じだしこれで解決かと思いきや、この手法で実装した場合のトレードオフももちろん存在します。（こういうサービス作ったことある人なら全部 **あるある** な課題）

* クライアントの状態管理
* `Media`を順番通りに読み込みたかったらどうする？
* スクロール中に新しい`Media`が生成されたら`offset`はどうなる？ページネーションもどうなる？
* エラー時処理どうする？（タイムアウトとか）

### デザイン時の課題

デザイン時のあるある課題。`Comment`コンポーネントを例に考えてみます。コメントの投稿者とコメントの内容を表示している簡単なコンポーネントです。

```jsx:Comment.js
class Comment extends React.Component {
  render() {
    var {comment} = this.props;
    return (
      <View>
        <Text>{comment.author.name}</Text>
        <Text>{comment.text}</Text>
      </View>
    );
  }
}
```

ここに投稿者の画像も表示しよう！ってなった場合にどうなるかって言うと


```jsx:Comment.js
class Comment extends React.Component {
  render() {
    var {comment} = this.props;
    return (
      <View>
        <Image uri={comment.author.photo} />
        <Text>{comment.author.name}</Text>
        <Text>{comment.text}</Text>
      </View>
    );
  }
}
```

１行増やすだけ！完了！
ってのはこの`Comment`コンポーネントの観点だけでいくと確かにそうなんですけど、その画像をどうやってサーバから取得するのかっていうところとか考え始めると、結局影響範囲ってのは計り知れなかったりします。（必要な以上にいろんなファイルをいじらないといけなくなる）

### データ書き込み時の課題
データ読み込み以上に複雑なのがデータ書き込み。どんなことを考えないといけないかというと、

* あるユーザがコメントを２つ生成した場合で、２つ目のコメントがサーバに先に到着した場合はどうする？
* あるいはそのレスポンスが順不同で返ってきたらどうする？
* コメントを２つ生成して、一つ目のコメントの書き込みが失敗したらどうする？
* サーバへのリクエストが単純に遅い場合はどうする？ペンディングしているのか失敗しているのかっていうのをユーザにどう伝える？
* 複数のユーザが同時に`Media`に対してコメントした場合、そのコメントを正しい順番で表示するためにはどうする？

などなど、いろいろな課題が存在します。

## D案：上以外の全く新しい方法
上のそれぞれの案を見てもわかるように、本筋とは違うところで開発者はいろんなことを考えないといけないです（いや、本当にそう思います）。それに加え、単純な変更をしようにも新しく入ったメンバーは影響範囲がどうなるかわからないのでラーニングコストが極めて高くなりがちです。コンポーネントが使う情報をサーバから宣言的(declaritive)に取得できる方法はないのか？？？

そこでfacebookが作ったのが`Relay`です。

# Relay
`Relay`は`GraphQL`を使っています。まず、`Comment`コンポーネントで使うデータを`GraphQL`で書くと↓のようになります。

```JSON
// GraphQL
Comment {
  text,
  author {
    name,
    photo
  }
}

// ↑によってサーバから取得できるJSONの例↓
{
  text: "Excited for React Europe!",
  author: {
    name: "Joe",
    photo: "https://..."
  }
}
```

これを`Relay`と`Comment`コンポーネントを組み合わせた場合にどのような実装になるかというと、↓みたいになります。

```js:Comment.js
class Comment extends React.Component {...} // 中身は省略

module.exports = Relay.createContainer(Comment, {
  queries: {
    comment: graphql`
      Comment {
        text,
        author {
          name,
          photo
        }
      }
    `
  }
});
```

今まで`React`で作ったコンポーネントは **そのまま使えます** 。`Relay`は`createContainer`の第一引数に`React`のコンポーネントを受け付けて、そのコンポーネントに必要な`props`をサーバから取得できた情報を基に渡してくれる役目を果たしてくれるのです。

## 概要（単一コンポーネント）
通常、`React`のコンポーネントは自身が使う情報を`props`という形で受け付けます。この`props`は **コンポーネントを使う側が、適切な情報を渡す責任があります**。

![スクリーンショット 2015-08-14 22.51.51.png](https://qiita-image-store.s3.amazonaws.com/0/38528/a185f3ea-5f2e-c59c-32e6-6ee7c38dcfe3.png "スクリーンショット 2015-08-14 22.51.51.png")


**使う側**が情報を取得してこないといけない(`ajax`やらでとってきてから`props`として渡してあげにといけない）ので、これが意外とめんどくさかったりするんですけど、`Relay`は、`React`のコンポーネントをラップして、サーバから必要な情報を取得した後にコンポーネントに`props`として渡してくれる役割を果たしてくれます。

![スクリーンショット 2015-08-14 22.58.58.png](https://qiita-image-store.s3.amazonaws.com/0/38528/2e96d9ac-e8dd-f8d6-041f-c0830ec5d31d.png "スクリーンショット 2015-08-14 22.58.58.png")

つまり`Relay`は例えばコメントの`id`を`props`として受けとって、その`id`をもとにサーバにリクエストを投げて、その結果を`Comment`コンポーネントの`props`として渡してくれるんです。

関数型な考え方で表現すると、通常の`React`コンポーネントは

`UI = view(props)`

という式で表現できるのに対して、`Relay`を使った場合は

`UI = view(query(props))`

という式で表現することができます。

## 概要（複数のコンポーネント）
`React`を使っていくと、コンポーネント基本的に様々なコンポーネントで構成されています（コンポジション）。例えば`Media`コンポーネントには`Comment`コンポーネントが含まれています。`Media`コンポーネントのコードは、↓のように書けます。

```jsx:Media.js
class Media extends React.Component {
  render() {
    var {media} = this.props;
    return (
      <View>
        <Header user={media.author} />
        <Image uri={media.uri} />
        <View>
          {media.comments.map(comment =>
            <Comment comment={comment} />
          )}
        </View>
      </View>
    );
  }
}
```

これに`Relay`を適用すると↓のようになります。

```jsx:Media.js
class Media extends React.Component {...} // 省略

module.exports = Relay.createContainer(Media, {
  queries: {
    media: graphql`
      Media {
        comments(first: 10) {
          edges {
            node {
              ${Comment.getQuery('comment')}
            }
          }
        }
      }
    `
  }
});
```

`Relay`で包むことで以下のことが起きます

1. エンドポイントから`Media`を取得
2. その際、その`Media`にくっついてる最初の10個の`comment`を取得
3. 結果を`Media`コンポーネントの`props`として渡す

`GraphQL`の構文についてはこのエントリで説明しません。全く知らない人は、とりあえず「こんなJSONがほしいの！」ってクエリを投げたらその形のJSONがサーバから返ってくるっていうふわっとした理解でこの場はOKです（多分）。

何が素晴らしいかって、特に素晴らしいのはやっぱり`Relay`が勝手に`React`コンポーネントにいい感じに`props`を渡してくれるところでしょう。例えば`Media`コンポーネントが新たにもっと情報がほしくなったときは`Relay`の`GraphQL`を書き換えることで魔法のように`props`が新たに提供されることになるんです。今までのように、自前で作った`ajax`の処理を触ったことによって、 **思わぬところに影響を及ぼしてしまう**という問題から解放されます。
さらに、`Relay`はコンポーネントの`render`が走る前に要求されたデータを取得済みにします。図で表現した場合↓のような流れになっています。
![スクリーンショット 2015-08-14 23.35.39.png](https://qiita-image-store.s3.amazonaws.com/0/38528/ce651900-23f6-4796-f1c5-3327be501604.png "スクリーンショット 2015-08-14 23.35.39.png")

これって、でもようするに **B案：オールインワンなカスタムエンドポイント** と一緒なんじゃないの？って疑問が浮かび上がってきます。`render`前に全情報を取得するってことは、その情報が全部揃わなかったら画面が表示されないってことになりますよね。
この問題を解決する方法も`Relay`には準備されています。
まず、先ほどの`Media`コンポーネントを↓のようにリファクタします。コメントを管理するためのコンポーネント`CommentList`を用意しました。

```jsx:Media.js
class Media extends React.Component {
  render() {
    var {media} = this.props;
    return (
      <View>
        <Header user={media.author} />
        <Image uri={media.uri} />
        <CommentList comments={media.comments} />
      </View>
    );
  }
}
```

あとは、このコンポーネントで`comments`があれば表示するし、そうでなければスピナー（読み込み中）を表示するロジックを入れておきます。

```jsx:Media.js
class Media extends React.Component {
  render() {
    var {media} = this.props;
    var comments;
    if (media.comments) {
      comments = <CommentList comments={media.comments} />;
    } else {
      comments = <Spinner />
    }
    return (
      <View>
        <Header user={media.author} />
        <Image uri={media.uri} />
        <CommentList comments={media.comments} />
      </View>
    );
  }
}
```

では、`Relay`は全部の情報が取得できてからコンポーネントを`render`するのですから、このままではSpinnerが表示されることはありません。そこで、どの情報は **遅延してもいいか**というのを`GraphQL`で`defer`を使って指定できるのです。↓のようになります。

```jsx:Media.js
class Media extends React.Component {...} // 省略

module.exports = Relay.createContainer(Media, {
  queries: {
    media: graphql`
      Media {
        ${CommentList.getQuery('comments').defer()},
        ... // 他のロジックは省略
      }
    `
  }
});
```

こうすることで↓のようなUXが実現可能になります。（コメントがあとから読み込まれている）
![スクリーンショット 2015-08-14 23.56.45.png](https://qiita-image-store.s3.amazonaws.com/0/38528/ebf336a7-359e-abf9-168a-b816a30b5033.png "スクリーンショット 2015-08-14 23.56.45.png")

## データの書き込みについて
サーバサイドで何が起きているか、までは詳しく述べられていませんが、概念てきには以下のような流れになるようです。（サーバサイドで何が起きているかは`GraphQL`をもっと勉強する必要がありそう。。。）

![スクリーンショット 2015-08-15 0.04.11.png](https://qiita-image-store.s3.amazonaws.com/0/38528/ab650dc3-c30d-2150-af92-9cb66aa0f929.png "スクリーンショット 2015-08-15 0.04.11.png")


* なんらかの`Action`が発生（例：`登録`ボタンのクリックとか）
* `Relay`が検知して、クエリをサーバに発行
* サーバからレスポンスを取得
* 対象コンポーネントに`props`として情報を提供

## 再描画の検知方法について
`React`は様々なコンポーネントが組み合わさって作られていますが、`Relay`を使った場合にあるデータに変化があった場合にどのコンポーネントを再描画すべきなのか、というのはどのように判断されているのでしょうか？？？
`Flux`だったらコンポーネントが`Store`の特定のイベントを監視して、変化があった場合に`props`が変わるので再描画が走ります。よって、たいていの場合コンポーネントマウント時にイベント購読して、コンポーネントがアンマウントされるタイミングでイベントの購読を解除します（開発者にそれを定義する義務があります）。
`Relay`はそれらを自動でやってくれるのに加えて、 **どのコンポーネントがどのデータを必要としているか**という情報を持っているので、データに変化があった場合にどのコンポーネントを再描画すべきかを **自動で判定**してくれます。コンポーネントの`shouldComponentUpdate`もいい感じに管理してくれるので、再描画が不要なコンポーネントもすべて管理してくれます。

# 結局`Relay`ってなんなの？
`Relay`の立ち位置について考えるよりも、`Relay`を使うことでどういった課題が解決できるか、というのをまとめています。

## 今まで開発者が抱えていた問題

* どうやってデータを取得すべきか
* サーバへのリクエスト時の整合性をどうすべきか
* サーバへの書き込み時の整合性をどうすべきか
* データの再取得をどうすべきか
* エラー時処理をどう考えるべきか
* サーバサイド・クライアントサイドの同期をどうとるべきか
* 画面の再描画をどう扱うべきか
* データのキャッシュをどう管理すべきか
* ↑の内容を頭の中でどう整理すべきか。。。

## `Relay`を導入するとどうなるか

* どうやってデータを取得すべきか
* ~~サーバへのリクエストの整合性をどうすべきか~~→どの順番でデータを取得すべきか
* ~~サーバへの書き込みの整合性をどうすべきか~~
* ~~データの再取得をどうすべきか~~
* ~~エラー時処理をどう考えるべきか~~
* ~~サーバサイド・クライアントサイドの同期をどうとるべきか~~
* ~~画面の再描画をどう扱うべきか~~
* ~~データのキャッシュをどう管理すべきか~~
* ~~↑の内容を頭の中でどう整理すべきか。。。~~

かなり大胆なことを言ってますが、

> Good APIs _simplify_ concerns.
> Declarative APIs _eliminate_ them.

優れたAPIはいろんな懸念事項をシンプルにしてくれる。
宣言的なAPIにはそもそも懸念事項が存在しない。
（↑意訳しすぎ？？？）

`React`が`DOM`の懸念事項を抹殺してくれたのと同じように`Relay`はデータ取得時の懸念事項を抹殺してくれるそうです。夢のような話ですね。facebookではproductionで使われている(GroupsとかAds Managerアプリは実際に`Relay`で作られているようです）ので、 **これは夢物語じゃなくてまじだ！**って言ってますけど、実際に使ったことがまだ無いので私はまだ半信半疑です（データ再取得とかエラー時処理とか実際はどうなってるのかとか）。。。が、上の内容が本当だとしたらとんでもないことになりそうですね！

ちなみに`Relay`のTechnical Previewは先日[オープンソース化](https://facebook.github.io/relay/)されているので、気になる方は色々と触ってみるしかないですね！私もワクワクが止まりません！（`GraphQL`の勉強も必須ですね）

ここまで読んでくださった方、ありがとうございます。冒頭にも書いていますが、理解が誤っている点や、わかりづらい点、誤字脱字等指摘がありましたらコメント欄にてお願いします。