LoginSignup
37
28

More than 5 years have passed since last update.

Graphcool、React、Apolloを使って、GraphQLのSubscriptionsを実装する

Last updated at Posted at 2018-03-31

はじめに

Graphcool、React、Apolloを使って、GraphQLのQueryとMutationを実装するの続編です。

前回では、GraphQLのQueryとMutationの実装を行い、記事の投稿と一覧表示ができるようになりました。

但し、前回までの実装では、投稿後にその投稿した記事を同じ画面で続けて表示したい場合は、ブラウザを手動でリロードさせなければならず、使い勝手の良いUXにはなっていませんでした。

そこで今回は、GraphQLのSubscriptionsという機能を用いてリアルタイムに記事が反映されるよう、前回作ったアプリを改修したいと思います。

縦横比が合っておらず、解像度も低い状態で恐縮ですが…、動画で実装後のイメージを載せました。

フォームから新規投稿するイメージ

新規投稿.mov.gif

別ドメインのGraphcoolのコンソールからデータ追加し、Subscriptionsによってフォームに自動反映されるイメージ

新規投稿2.mov.gif

別ドメインのGraphcoolからデータ更新(編集)して、フォームに自動反映されるイメージ

編集.mov.gif

Subscriptionsについて

subscriptionという英単語を辞書で引いてみると、購読が一つの訳として当てはまりますが、GraphQLのSubscriptionsに関しては、GraphQLのサーバー側で扱うモデルのデータに変化が生じる度にプッシュし、クライアント側でその変化をリアルタイムに検知して、変化後のデータを自由に扱うことができる機能、または仕組みと言えるかと思います。

実装

Web Socket接続用ライブラリのインストール

GraphQLサーバーへの接続用クライアントライブラリとして、前回でも用いたApolloを使いますが、ApolloはWeb Socketに基づいてSubscriptionsを実装していますので、ApolloがWeb Socketを扱えるように、次の2つのパッケージをインストールします。

yarn add apollo-link-ws subscriptions-transport-ws

Web Socketリンクの作成

Subscriptions用のAPIエンドポイントを用いて、Web Socketリンクを作ります。

react-graphcool-sample/src/index.js
const SUBSCRIPTIONS_ENDPOINT = "wss://subscriptions.graph.cool/v1/xxxxxxxxxxx";
const wsLink = new WebSocketLink({
  uri: SUBSCRIPTIONS_ENDPOINT,
  options: {
    reconnect: true
  }
});

前回の記事でGraphcool CLIを導入済みの方は、graphcool initによって作成されたディレクトリ内に移動し、graphcool infoコマンドを実行することによって、エンドポイントを確認することができます。

graphcool info

Service Name Cluster / Service ID
──────────── ────────────────────────────────────────────
server       shared-eu-west-1/xxxxxxxx

API:           Endpoint:
────────────── ────────────────────────────────────────────────────────────
Simple         https://api.graph.cool/simple/v1/xxxxxxxx
Relay          https://api.graph.cool/relay/v1/xxxxxxxx
Subscriptions  wss://subscriptions.graph.cool/v1/xxxxxxxx
File           https://api.graph.cool/file/v1/xxxxxxxx

split関数によるリンクの使い分け

Web Socketリンクに、前回から使っていたHttpリンクとあわせて、GraphQLの操作に応じて、split関数を用いてこの2つのリンクを使い分けます。
getMainDefinition(query)kindoperationをそれぞれキーとした要素を持つオブジェクトを返すので、オブジェクトの分割代入によってkindoperationにそれぞれ値を格納し、GraphQLの操作がsubscriptionかそうでないかを判別しています。
判別した結果によって、linkに格納する値(wsLinkまたはhttpLink)を決めています。

react-graphcool-sample/src/index.js
// 発行されるqueryの種類によって、接続先を切り替える
// split()は第一引数がtrueならば第二引数を、falseならば第三引数を返す
const link = split(
  ({ query }) => {
    const { kind, operation } = getMainDefinition(query);
    return kind === 'OperationDefinition' && 
           operation === 'subscription';
  },
  wsLink,
  httpLink,
);

Subscription向けqueryを生成

Postというモデルに対して、記事の新規作成(CREATED)と、記事の更新(UPDATED)に関する情報をリアルタイムに受け取るには、次のようなqueryを発行します。

react-graphcool-sample/src/components/PostList.js
const POSTS_SUBSCRIPTION = gql`
  subscription updatePost {
    Post(
      filter: {
        mutation_in: [CREATED, UPDATED] # 購読対象のmutationの種類を指定
      }
    ) {
      mutation # 実行されたmutationの種類を出力
      node { # 変化後のデータを出力
        id
        title
        content
      }
      updatedFields # UPDATEされたフィールドを出力
      previousValues { # 変化前のデータを出力
        id
        title
        content
      }
    }
  }
`;

こちらの実行は、Graphcoolコンソールのplaygroundで試すことができます。

PostListの再実装

上記query(POSTS_SUBSCRIPTION)をPostListコンポーネントに組み込みます。

データの変化を購読(自動検知)するために、props.allPostsQueryの中に含まれるsubscribeToMoreという関数を、コンポーネントがレンダリングされた直後に一度だけ実行されるcomponentDidMount内で実行します。

subscribeToMore()の引数には、documentupdateQueryをそれぞれキーとして持つ要素が格納されたオブジェクトを指定します。

documentの値には上記POSTS_SUBSCRIPTIONを当てはめ、updateQueryの値には、POSTS_SUBSCRIPTIONが実行される度に実行されるコールバック関数を指定します。

react-graphcool-sample/src/components/PostList.js
class PostList extends Component {
  componentDidMount() {
    this.props.allPostsQuery.subscribeToMore({
      document: POSTS_SUBSCRIPTION,
      updateQuery: (prev, { subscriptionData }) => {
        let result = { allPosts: prev.allPosts };

        if (subscriptionData.data.Post.mutation === "CREATED") {
          const newPosts = [
            ...prev.allPosts,
            subscriptionData.data.Post.node,
          ];
          console.log('newPosts', newPosts);
          result = { allPosts: newPosts };
        }
        console.log('prev', prev);
        console.log('sub', subscriptionData.data);        

        return result;
      },
    });
  }

UPDATEDの場合にはprev.allPostsの中身が更新されているので、それをそのままreturnしていますが、CREATEDの場合には更新されておらず、新規作成されたデータをsubscriptionData.data.Post.nodeで取り出し、prev.allPostsの中身にマージしたものをreturnしています。

prevsubおよびnewPostsのそれぞれの値には何が格納されているのか、console.log()によって出力していますので、是非確認してみてください。

Subscription実装後のソース

クローンもしくはダウンロード後、下記手順にてアプリを起動することができます。

  1. プロジェクトのルートでyarnを実行して、パッケージをインストール
  2. src/index.jsにて、2つのエンドポイントを設定
  3. yarn startを実行して、アプリを起動

参考情報

37
28
0

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
37
28