GraphQL ミューテーション
GraphQL では、サーバー上のデータは GraphQL ミューテーションを使用して更新されます。ミューテーションは読み書き可能なサーバー操作であり、バックエンドのデータを変更すると同時に、同じリクエストで変更されたデータをクエリできます。
ミューテーションの記述
GraphQL ミューテーションは、mutation
キーワードを使用する点を除いて、クエリと非常によく似ています。
mutation FeedbackLikeMutation($input: FeedbackLikeData!) {
feedback_like(data: $input) {
feedback {
id
viewer_does_like
like_count
}
}
}
- 上記のミューテーションは、指定された
Feedback
オブジェクトを「いいね」するようにサーバーデータを変更します。 feedback_like
は、バックエンドのデータを更新する *ミューテーションルートフィールド* (または単に *ミューテーションフィールド*) です。
- ミューテーションは、2 つのステップで処理されます。最初に、サーバー上で更新が処理され、次にクエリが実行されます。これにより、ミューテーションの応答の一部としてすでに更新されたデータのみが表示されるようになります。
クエリも同じように処理されることに注意してください。外側の選択は、内側の選択の前に計算されます。最上位のミューテーションフィールドには副作用があり、他のフィールドにはない傾向があるというのは、単なる慣例の問題です。
- ミューテーションフィールド (この場合は
feedback_like
) は、ミューテーション応答でクエリできるデータを公開する特定の GraphQL 型を返します。
- この場合、更新された
like_count
と、現在の閲覧者がフィードバックオブジェクトを気に入っているかどうかを示す、更新されたviewer_does_like
の値を含む、*更新された* フィードバックオブジェクトをクエリしています。
上記のミューテーションが成功した場合の応答の例は次のようになります。
{
"feedback_like": {
"feedback": {
"id": "feedback-id",
"viewer_does_like": true,
"like_count": 1,
}
}
}
Relay では、graphql
タグを使用して GraphQL ミューテーションを宣言することもできます。
const {graphql} = require('react-relay');
const feedbackLikeMutation = graphql`
mutation FeedbackLikeMutation($input: FeedbackLikeData!) {
feedback_like(data: $input) {
feedback {
id
viewer_does_like
like_count
}
}
}
`;
- ミューテーションは、クエリやフラグメントと同じように、GraphQL 変数を参照することもできることに注意してください。
useMutation
を使用したミューテーションの実行
Relay でサーバーに対してミューテーションを実行するには、commitMutation
および useMutation API を使用できます。useMutation
API を使用した例を見てみましょう。
import type {FeedbackLikeData, LikeButtonMutation} from 'LikeButtonMutation.graphql';
const {useMutation, graphql} = require('react-relay');
function LikeButton({
feedbackId: string,
}) {
const [commitMutation, isMutationInFlight] = useMutation<LikeButtonMutation>(
graphql`
mutation LikeButtonMutation($input: FeedbackLikeData!) {
feedback_like(data: $input) {
feedback {
viewer_does_like
like_count
}
}
}
`
);
return <button
onClick={() => commitMutation({
variables: {
input: {id: feedbackId},
},
})}
disabled={isMutationInFlight}
>
Like
</button>
}
ここで何が起こっているかを分解してみましょう。
useMutation
は、ミューテーションを含む graphql リテラルを唯一の引数として取ります。- アイテムのタプルを返します。
UseMutationConfig
を受け入れるコールバック (commitMutation
を呼び出します) と- ミューテーションが進行中かどうかを示すブール値。
- さらに、
useMutation
は Flow 型パラメーターを受け入れます。クエリと同様に、ミューテーションの Flow 型は、Relay コンパイラーが生成するファイルからエクスポートされます。- この型が提供されている場合、
UseMutationConfig
も静的に型指定されます。**この型を常に提供することをお勧めします。**
- この型が提供されている場合、
- さて、
commitMutation
がミューテーション変数とともに呼び出されると、Relay はサーバー上でfeedback_like
フィールドを実行するネットワークリクエストを行います。この例では、これにより、変数で指定されたフィードバックが見つかり、ユーザーがそのフィードバックを気に入ったことをバックエンドに記録します。 - そのフィールドが実行されると、バックエンドは更新されたフィードバックオブジェクトを選択し、そこから
viewer_does_like
およびlike_count
フィールドを選択します。Feedback
型にはid
フィールドが含まれているため、Relay コンパイラーはid
フィールドの選択を自動的に追加します。
- ミューテーション応答を受信すると、Relay は一致する
id
を持つストア内のフィードバックオブジェクトを見つけ、新しく受信したviewer_does_like
およびlike_count
の値で更新します。 - これらの値が結果として変更された場合、フィードバックオブジェクトからこれらのフィールドを選択したコンポーネントは再レンダリングされます。または、口語的に言えば、更新されたデータに依存するコンポーネントは再レンダリングされます。
パラメーター FeedbackLikeData
の型の名前は、最上位のミューテーションフィールドの名前、つまり feedback_like
から派生しています。この型は、生成された graphql.js
ファイルからもエクスポートされます。
ミューテーションに応じたコンポーネントの更新
前の例では、viewer_does_like
と like_count
を手動で選択しました。これらのフィールドを選択するコンポーネントは、これらのフィールドの値が変更された場合に再レンダリングされます。
ただし、一般的には、ミューテーションに応答して更新したいコンポーネントに対応するフラグメントをスプレッドする方が優れています。これは、コンポーネントによって選択されたデータが変更される可能性があるためです。
開発者にコンポーネントのデータに影響を与える可能性のあるすべてのミューテーションについて知っておくこと (およびそれらを最新の状態に保つこと) を要求することは、Relay が要求を避けたいグローバルな推論の一例です。
たとえば、ミューテーションを次のように書き換える場合があります。
mutation FeedbackLikeMutation($input: FeedbackLikeData!) {
feedback_like(data: $input) {
feedback {
...FeedbackDisplay_feedback
...FeedbackDetail_feedback
}
}
}
このミューテーションが実行されると、FeedbackDisplay
および FeedbackDetail
コンポーネントによって選択されたフィールドが再度フェッチされ、これらのコンポーネントは一貫した状態を維持します。
フラグメントをスプレッドすることは、ミューテーションが完了した後にデータを再度フェッチするよりも一般的に望ましいです。更新されたデータは 1 回のラウンドトリップでフェッチできるためです。
ミューテーションが完了またはエラーになったときにコールバックを実行する
ミューテーションの成功または失敗に応じて、一部の状態を更新したい場合があります。たとえば、ミューテーションが失敗した場合にユーザーに警告したい場合があります。UseMutationConfig
オブジェクトには、このようなケースを処理するための次のフィールドを含めることができます。
onCompleted
は、ミューテーションが完了したときに実行されるコールバックです。ミューテーションの応答 (フラグメントスプレッドの境界で停止) が渡されます。onCompleted
に渡される値は、アップデーターと宣言的なミューテーションディレクティブが適用された **後**、ストアから読み取られたミューテーションフラグメントです。つまり、マスクされていないフラグメント内のデータは読み取られず、削除されたレコード (例:@deleteRecord
による) も null になる可能性があります。
onError
は、ミューテーションがエラーになったときに実行されるコールバックです。発生したエラーが渡されます。
宣言的なミューテーションディレクティブ
ミューテーションに応じた接続の操作
Relay を使用すると、接続 (つまりリスト) にアイテムを追加したり、接続からアイテムを削除したりすることで、ミューテーションに簡単に応答できます。たとえば、新しく作成されたユーザーを特定の接続に追加する場合があります。詳細については、宣言的なディレクティブの使用を参照してください。
ミューテーションに応じたアイテムの削除
さらに、ミューテーションに応じてストアからアイテムを削除したい場合があります。これを行うには、削除された ID に @deleteRecord
ディレクティブを追加します。例:
mutation DeletePostMutation($input: DeletePostData!) {
delete_post(data: $input) {
deleted_post {
id @deleteRecord
}
}
}
ローカルデータの命令的な変更
場合によっては、実行したい更新がフィールドの値の更新よりも複雑で、宣言的なミューテーションディレクティブで処理できないことがあります。このような状況では、UseMutationConfig
は、ストアを更新する方法を完全に制御できる updater
関数を受け入れます。
これについては、ストアデータの命令的な変更のセクションで詳しく説明します。
オプティミスティックアップデート
多くの場合、ユーザーの操作に応答する前に、サーバーからの応答を待つ必要はありません。たとえば、ユーザーが「いいね」ボタンをクリックした場合、影響を受けたコメント、投稿などがユーザーにいいねされたことをすぐに表示したいと思います。
一般的に、このような場合、ミューテーションが正常に完了すると仮定して、ストア内のデータをすぐにオプティミスティックに更新したいと考えます。ミューテーションが最終的に成功しなかった場合は、そのオプティミスティックな更新をロールバックしたいと考えます。
オプティミスティックな応答
これを有効にするために、UseMutationConfig
には optimisticResponse
フィールドを含めることができます。
このフィールドを Flow 型にするには、useMutation
の呼び出しに Flow 型パラメーターを渡し、ミューテーションに @raw_response_type
ディレクティブを付加する必要があります。
前の例では、次のようなオプティミスティックな応答を提供する可能性があります。
{
feedback_like: {
feedback: {
// Even though the id field is not explicitly selected, the
// compiler selected it for us
id: feedbackId,
viewer_does_like: true,
},
},
}
さて、commitMutation
を呼び出すと、このデータはすぐにストアに書き込まれます。一致する id を持つストア内のアイテムは、viewer_does_like
の新しい値で更新されます。このフィールドを選択したコンポーネントはすべて再レンダリングされます。
ミューテーションが成功またはエラーになった場合、オプティミスティックな応答はロールバックされます。
like_count
フィールドを更新するには、もう少し作業が必要です。更新するには、コンポーネントで **現在のいいね数** も読み取る必要があります。
import type {FeedbackLikeData, LikeButtonMutation} from 'LikeButtonMutation.graphql';
import type {LikeButton_feedback$fragmentType} from 'LikeButton_feedback.graphql';
const {useMutation, graphql} = require('react-relay');
function LikeButton({
feedback: LikeButton_feedback$fragmentType,
}) {
const data = useFragment(
graphql`
fragment LikeButton_feedback on Feedback {
__id
viewer_does_like @required(action: THROW)
like_count @required(action: THROW)
}
`,
feedback
);
const [commitMutation, isMutationInFlight] = useMutation<LikeButtonMutation>(
graphql`
mutation LikeButtonMutation($input: FeedbackLikeData!)
@raw_response_type {
feedback_like(data: $input) {
feedback {
viewer_does_like
like_count
}
}
}
`
);
const changeToLikeCount = data.viewer_does_like ? -1 : 1;
return <button
onClick={() => commitMutation({
variables: {
input: {id: data.__id},
},
optimisticResponse: {
feedback_like: {
feedback: {
id: data.__id,
viewer_does_like: !data.viewer_does_like,
like_count: data.like_count + changeToLikeCount,
},
},
},
})}
disabled={isMutationInFlight}
>
Like
</button>
}
オプティミスティックなレスポンスの値がストアの値に依存する場合、およびそのストアの値に影響を与えるオプティミスティックなレスポンスが複数存在する場合は、オプティミスティックアップデーターの使用を検討し、注意する必要があります。
たとえば、2つのオプティミスティックなレスポンスがそれぞれ「いいね」の数を1つ増やし、最初のオプティミスティックアップデーターがロールバックされた場合でも、2番目のオプティミスティックな更新は適用され、ストア内の「いいね」の数は2つ増えたままになります。
オプティミスティックなレスポンスには多くの落とし穴があります!
- オプティミスティックなレスポンスには、フラグメントのスプレッドの内容を含む、完全なクエリレスポンスのデータを含めることができます。これは、開発者がオプティミスティックなレスポンスでフラグメントがスプレッドされているコンポーネントでより多くのフィールドを選択した場合、これらのコンポーネントはオプティミスティックな更新中に矛盾したデータまたは部分的なデータを持つ可能性があることを意味します。
- オプティミスティックな更新の型には、再帰的にネストされたすべてのフラグメントの内容が含まれるため、非常に大きくなる可能性があります。特定のミューテーションに
@raw_response_type
を追加すると、Relayコンパイラのパフォーマンスが低下する可能性があります。
オプティミスティックアップデーター
オプティミスティックなレスポンスだけでは、すべての場合に対応できるわけではありません。たとえば、ミューテーションで選択していないデータをオプティミスティックに更新したい場合があります。または、コネクションからアイテムを追加または削除したい場合があります(宣言的なミューテーションディレクティブでは対応できない場合)。
このような場合、UseMutationConfig
にはoptimisticUpdater
フィールドを含めることができ、開発者はストア内のデータを命令的に、かつオプティミスティックに更新できます。これについては、ストアデータを命令的に更新するのセクションで詳しく説明します。
アップデーター関数の実行順序
一般に、updater
とオプティミスティックな更新の実行は、次の順序で行われます。
optimisticResponse
が提供されている場合、そのデータがストアに書き込まれます。optimisticUpdater
が提供されている場合、Relayはそれを実行し、それに応じてストアを更新します。optimisticResponse
が提供されていた場合、ミューテーションに存在する宣言的なミューテーションディレクティブがオプティミスティックなレスポンスに対して処理されます。- ミューテーションリクエストが成功した場合
- 適用されたすべてのオプティミスティックな更新がロールバックされます。
- Relayはサーバーからのレスポンスをストアに書き込みます。
updater
が提供されていた場合、Relayはそれを実行し、それに応じてストアを更新します。サーバーのペイロードは、ストアのルートフィールドとしてupdater
で使用できます。- Relayは、サーバーからのレスポンスを使用して、宣言的なミューテーションディレクティブを処理します。
onCompleted
コールバックが呼び出されます。
- ミューテーションリクエストが失敗した場合
- 適用されたすべてのオプティミスティックな更新がロールバックされます。
onError
コールバックが呼び出されます。
ミューテーション中のデータの無効化
ミューテーションを実行する際の推奨されるアプローチは、ローカルのRelayストアがサーバーの状態と一致するように、ミューテーションによって影響を受けた関連データすべてをサーバーから(ミューテーションの本文の一部として)リクエストすることです。
ただし、影響が大きく波及するミューテーション(例:「ユーザーをブロックする」や「グループを離れる」を想像してください)の場合、影響を受ける可能性のあるすべてのデータを把握して指定することは現実的でない場合があります。
これらのタイプのミューテーションの場合、一部のデータを(またはストア全体を)明示的に古いものとしてマークする方が、Relayが次にレンダリングされるときにリフェッチするように指示するのに、より直接的であることがよくあります。そのためには、データの鮮度セクションで説明されているデータ無効化APIを使用できます。
このページは役に立ちましたか?
サイトをさらに改善するために いくつかの簡単な質問にご協力ください.