はじめに
Graphcool、React、Apolloを使って、GraphQLのQueryとMutationを実装するの続編です。
前回では、GraphQLのQueryとMutationの実装を行い、記事の投稿と一覧表示ができるようになりました。
但し、前回までの実装では、投稿後にその投稿した記事を同じ画面で続けて表示したい場合は、ブラウザを手動でリロードさせなければならず、使い勝手の良いUXにはなっていませんでした。
そこで今回は、GraphQLのSubscriptions
という機能を用いてリアルタイムに記事が反映されるよう、前回作ったアプリを改修したいと思います。
縦横比が合っておらず、解像度も低い状態で恐縮ですが…、動画で実装後のイメージを載せました。
フォームから新規投稿するイメージ
別ドメインのGraphcoolのコンソールからデータ追加し、Subscriptionsによってフォームに自動反映されるイメージ
別ドメインのGraphcoolからデータ更新(編集)して、フォームに自動反映されるイメージ
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リンクを作ります。
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)
がkind
、operation
をそれぞれキーとした要素を持つオブジェクトを返すので、オブジェクトの分割代入によってkind
とoperation
にそれぞれ値を格納し、GraphQLの操作がsubscription
かそうでないかを判別しています。
判別した結果によって、link
に格納する値(wsLink
またはhttpLink
)を決めています。
// 発行される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を発行します。
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()
の引数には、document
、updateQuery
をそれぞれキーとして持つ要素が格納されたオブジェクトを指定します。
document
の値には上記POSTS_SUBSCRIPTION
を当てはめ、updateQuery
の値には、POSTS_SUBSCRIPTIONが実行される度に実行されるコールバック関数を指定します。
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しています。
prev
、sub
およびnewPosts
のそれぞれの値には何が格納されているのか、console.log()によって出力していますので、是非確認してみてください。
Subscription実装後のソース
クローンもしくはダウンロード後、下記手順にてアプリを起動することができます。
- プロジェクトのルートで
yarn
を実行して、パッケージをインストール - src/index.jsにて、2つのエンドポイントを設定
-
yarn start
を実行して、アプリを起動