React & Amplify で GraphQLの subscription 使ってみた「俺の理解」
前回書いた、とりあえず動かす!Cloud9 & React & Amplify & GraphQLの環境構築を基にGraphQLのsubscriptionについてメモがわりにまとめました。もともとAmplifyに手を出したキッカケが、「サーバからリアルタイムでデータ取りたいなぁ〜」「Reactで即時反映ってできないかなぁ〜」ってことでした。なので、この記事がある意味では本命記事になります!!
参考にした記事は前回とほぼ同じです。
前提条件
とりあえず動かす!Cloud9 & React & Amplify & GraphQLの環境構築にあるschemaとApp.jsを基に説明します。GraphQLのsubscriptionについて書いた記事ですが、前回の記事で書いたschemaとApp.jsのsubscription部分で、ただひたすら自分が理解したことを説明するだけです。
schemaのこと
schema, スキーマと言ってますが、実態は以下のフォルダにある「schema.graphql」ファイルの説明です。
- reactプロジェクト配下/amplify/backend/api/{api名}/
定義は以下の通りです。
type Todo
@model
@key(name: "SortByName", fields:["owner", "name"], queryField: "listTodosSortedByName" )
{
id: ID!
name: String!
owner: String
updatedAt: AWSDateTime
createdAt: AWSDateTime
}
ここで重要なのはディレクティブの「@model」です。@modelをつけるとDynamoDBにテーブルが生成されます。またそのテーブルのデータを操作するために、query, mutation, subscriptionがフロントエンドにはファイルとして、バックエンド(AppSync)にはschema定義として生成されます。@modelの後ろにquery, mutation, subscriptionの定義をそれぞれ記述しない場合は、自動で以下の定義ファイルとデータ操作に必要なリゾルバが生成されます。(詳細は公式を参照)
上記のschemaは@modelにquery, mutation, subscriptionのいずれも付与していないので、デフォルトの定義ファイルとリゾルバが生成されます。また5つの項目があるテーブルがDynamoDBに生成されます。
- query
- リゾルバ:get, list
- mutation
- リゾルバ:create, update, delete
- subscription
- リゾルバ:onCreate, onUpdate, onDelete
この時点での理解は、
- schema : テーブルとそこに入るデータの型を定義するもので、定義した型以外を受け付けないように、定義した以外の操作を受け付けないように制御するもの
- query, mutation, subscription : DynamoDBのデータ操作をするもの
- query : 単一、または複数のデータ取得を行う
- mutation : データの登録 / 更新 / 削除を行う
- subscription : mutationをトリガーにバックエンドからフロントエンドへデータの即時反映(リアルタイム更新)を行う
- リゾルバ : ↑3つのデータ操作で詳細な処理を行うメソッドのようなもの
といった程度です。(自分の理解をそのまま文字にしたので、内容は厳密な定義との大きな乖離があります。コメントとご指摘をください。)
App.jsのこと
下記のApp.jsに書き換えました。
import React, {useState, useEffect, useReducer } from 'react';
import API, { graphqlOperation } from '@aws-amplify/api';
import { createTodo } from './graphql/mutations';
import { onCreateTodo } from './graphql/subscriptions';
import './App.css';
const QUERY = 'QUERY';
const SUBSCRIPTION = 'SUBSCRIPTION';
const initialState = {
todos: [],
};
const reducer = (state, action) => {
switch (action.type) {
case QUERY:
return {...state, todos: action.todos};
case SUBSCRIPTION:
return {...state, todos:[...state.todos, action.todo]}
default:
return state;
}
};
async function createNewTodo() {
const todo = { name: "Todo " + Math.floor(Math.random() * 10) };
await API.graphql(graphqlOperation(createTodo, { input: todo }));
}
function App() {
const [state, dispatch] = useReducer(reducer, initialState);
const [user] = useState(null);
useEffect(() => {
const subscription = API.graphql(graphqlOperation(onCreateTodo)).subscribe({
next: (eventData) => {
const todo = eventData.value.data.onCreateTodo;
dispatch({ type: SUBSCRIPTION, todo });
}
});
return () => subscription.unsubscribe();
}, []);
return (
<div className="App">
<p>user: {user!= null && user.username}</p>
<button onClick={createNewTodo}>Add Todo</button>
<div>
{state.todos.length > 0 ?
state.todos.map((todo) => <p key={todo.id}>{todo.name} ({todo.createdAt})</p>) :
<p>Add some todos!</p>
}
</div>
</div>
);
}
export default App;
重要な点はデータ登録の
await API.graphql(graphqlOperation(createTodo, { input: todo }));
部分と、データ登録をトリガーにしたリアルタイム反映の
const subscription = API.graphql(graphqlOperation(onCreateTodo)).subscribe({
next: (eventData) => {
const todo = eventData.value.data.onCreateTodo;
dispatch({ type: SUBSCRIPTION, todo });
}
});
部分の2箇所です。順番に説明します。
データ登録
登録 / 更新 / 削除をトリガーにしてリアルタイムにデータの反映が行われるため、データ登録の処理はリアルタイム反映であるsubscriptionと関連がある内容です。
まず、amplifyのquery, mutation, subscriptionをReactで呼び出すために、以下のライブラリをインポートします。
import API, { graphqlOperation } from '@aws-amplify/api';
import { createTodo } from './graphql/mutations';
この時点での理解は以下2点です。
- amplifyのデータ処理を行うgraphqlOperationを利用するため、amplifyのライブラリをReactにインポートする
- src/graphql/mutations.jsにあるcreateTodo(リゾルバ)を登録処理で使うために、Reactにインポートする
そして下記の通り、ボタン押下によりデータ登録処理が呼び出され、mutationのcreateTodoによりデータ登録を行います。
<button onClick={createNewTodo}>Add Todo</button>
createNewTodoの処理でmutationのcreateTodoを呼び出しています。createTodoの引数に以下のtodoを指定しています。
const todo = { name: "Todo " + Math.floor(Math.random() * 10) };
schema定義でテーブルのidはID型であるため、自動生成された値が登録されるので、引数の指定は不要ですが、nameは文字列で必須入力のため、引数に指定が必要となります。
次に説明するsubscriptionでこの「createTodo」が重要になります。
リアルタイム反映(即時更新)
やっと本題中の本題です。
subscriptionを利用するためのライブラリを以下の通りインポートします。
import { onCreateTodo } from './graphql/subscriptions';
これは登録で記載したのと同様に
- src/graphql/subscriptions.jsにあるonCreateTodo(リゾルバ)を登録処理トリガーにして、即時反映で使うために、Reactにインポートする
リアルタイム反映のsubscriptionはmutaitonのcreate, update, deleteで定義したリゾルバ(今回はcreateのcreateTodo)をトリガーに処理が行われます。
改めてsubscriptionの処理を確認すると
const subscription = API.graphql(graphqlOperation(onCreateTodo)).subscribe({
next: (eventData) => {
const todo = eventData.value.data.onCreateTodo;
dispatch({ type: SUBSCRIPTION, todo });
}
});
となっていて、
API.graphql(graphqlOperation(onCreateTodo)).subscribe
の部分で、mutaitonのcreate, update, deleteの内どれがトリガーとなっているのかという部分を、subscriptionで定義しています。今回、そのトリガーとなるイベントはmutationのcreateした際に呼び出されるonCreateです。
また、subscribeの引数「eventData」を利用して、「onCreateした時登録したデータ」をsubscriptionの処理で利用できます。今回は画面表示しているだけですが、登録されたデータによって処理を条件分岐するなど、利用の方法はたくさんありそうです。(多分、、)
終わりに
自分の頭の中にある「amplifyのsubscriptionはこういう理解だ!」という部分を書き出してみました。厳密な定義とは乖離しますが、とりあえず動くものを作る程度であれば、自分にはこの理解でも十分だったかなと思います。
Discussion