ReactアプリでApollo-Clientを使用してクライアント側のGraphQlを理解する

公開: 2022-03-10
簡単な要約↬クライアント側のアプリケーションでGraphQLサーバーと対話しようとして、どこにでも行く前に諦めたくなったことはありませんか? わからなかったために、GraphQL APIの操作を必要とするコードベースへの招待を断ったことがありますか? GraphQL APIの使用方法を学んでいない唯一のフロントエンドエンジニアのように感じたことはありますか? これらの質問のいずれかに「はい」と答えた場合、このチュートリアルはあなたにぴったりです。 GraphQLとApolloClientのいくつかの基本と、両方を操作する方法について詳しく見ていきます。 最後に、ApolloClientを使用するペットショップアプリを作成します。 次に、次のプロジェクトの構築に進むことができます。

State of JavaScript 2019によると、開発者の38.7%がGraphQLを使用したいと考えており、開発者の50.8%がGraphQLを学びたいと考えています。

クエリ言語であるGraphQLは、クライアントアプリケーションを構築するワークフローを簡素化します。 必要なデータをフェッチするための単一のHTTPエンドポイントを公開するため、クライアント側アプリでのAPIエンドポイントの管理の複雑さが解消されます。 したがって、RESTの場合のように、データのオーバーフェッチとアンダーフェッチが排除されます。

しかし、GraphQLは単なるクエリ言語です。 それを簡単に使用するために、私たちは私たちのために重い物を持ち上げるプラットフォームが必要です。 そのようなプラットフォームの1つがApolloです。

Apolloプラットフォームは、クラウド(サーバー)間でアプリのUIにデータを転送するGraphQLの実装です。 Apollo Clientを使用する場合、データの取得、追跡、ロード、およびUIの更新のためのすべてのロジックは、 useQueryフックによってカプセル化されます(Reactの場合のように)。 したがって、データフェッチは宣言型です。 また、ゼロ構成キャッシングも備えています。 アプリでApolloClientを設定するだけで、追加の構成を必要とせずに、すぐに使用できるインテリジェントキャッシュを利用できます。

Apollo Clientは、Angular、Vue.js、Reactなどの他のフレームワークとも相互運用可能です。

このチュートリアルは、過去にクライアント側でRESTfulまたは他の形式のAPIを使用したことがあり、GraphQLを試してみる価値があるかどうかを確認したい人に役立ちます。 これは、以前にAPIを使用していたはずであることを意味します。 そうして初めて、GraphQLが自分にとってどれほど有益であるかを理解できるようになります。 GraphQLとApolloClientのいくつかの基本について説明しますが、JavaScriptとReactHooksに関する十分な知識が役立ちます。

GraphQLの基本

この記事はGraphQLの完全な紹介ではありませんが、続行する前にいくつかの規則を定義します。

GraphQLとは何ですか?

GraphQLは、クライアントが必要な正確なデータをAPIに要求するために使用できる宣言型クエリ言語を記述する仕様です。 これは、APIの強力な型スキーマを作成し、究極の柔軟性を実現することで実現されます。 また、APIがデータを解決し、クライアントクエリがスキーマに対して検証されることを保証します。 この定義は、GraphQLに、静的に型付けされた(Typescriptを中心に構築された)APIを備えた宣言型クエリ言語を作成するいくつかの仕様が含まれていることを意味し、クライアントがそれらの型システムを利用してAPIに必要な正確なデータを要求できるようにします。

したがって、いくつかのフィールドを含むいくつかのタイプを作成した場合、クライアント側から、「これらの正確なフィールドを含むこのデータを提供してください」と言うことができます。 次に、APIは、強く型付けされた言語で型システムを使用しているかのように、その正確な形状で応答します。 詳細については、私のTypescriptの記事をご覧ください。

続行するときに役立つGraphQlのいくつかの規則を見てみましょう。

基礎

  • 操作
    GraphQLでは、実行されるすべてのアクションは操作と呼ばれます。 いくつかの操作があります。つまり、次のとおりです。
    • クエリ
      この操作は、サーバーからのデータのフェッチに関係しています。 読み取り専用フェッチと呼ぶこともできます。
    • 突然変異
      この操作には、サーバーからのデータの作成、更新、および削除が含まれます。 これは一般にCUD(作成、更新、および削除)操作と呼ばれます。
    • サブスクリプション
      GraphQLでのこの操作には、特定のイベントが発生したときにサーバーからクライアントにデータを送信することが含まれます。 これらは通常、WebSocketで実装されます。

この記事では、クエリとミューテーションの操作のみを扱います。

  • 操作
    クライアント側のクエリおよびミューテーション操作には一意の名前があります。
  • 変数と引数
    操作は、ほとんどのプログラミング言語の関数と非常によく似た引数を定義できます。 これらの変数は、引数として操作内のクエリまたはミューテーション呼び出しに渡すことができます。 変数は、クライアントからの操作の実行中に実行時に指定されることが期待されます。
  • エイリアシング
    これは、クライアント側のGraphQLの規則であり、UIの単純で読みやすいフィールド名を使用して、冗長またはあいまいなフィールド名の名前を変更します。 競合するフィールド名を使用したくないユースケースでは、エイリアシングが必要です。
GraphQLの基本的な規則
GraphQLの基本的な規則。 (大プレビュー)
ジャンプした後もっと! 以下を読み続けてください↓

クライアントサイドGraphQLとは何ですか?

フロントエンドエンジニアがVue.jsや(この場合は)Reactなどのフレームワークを使用してUIコンポーネントを構築する場合、これらのコンポーネントは、サーバーからフェッチされるデータに合わせて、クライアント上の特定のパターンからモデル化および設計されます。

RESTful APIの最も一般的な問題の1つは、オーバーフェッチとアンダーフェッチです。 これは、クライアントがデータをダウンロードする唯一の方法が、固定データ構造を返すエンドポイントをヒットすることであるために発生します。 このコンテキストでのオーバーフェッチは、クライアントがアプリで必要とされるよりも多くの情報をダウンロードすることを意味します。

一方、GraphQLでは、必要なデータを含む単一のクエリをGraphQLサーバーに送信するだけです。 サーバーは、要求した正確なデータのJSONオブジェクトで応答するため、オーバーフェッチは発生しません。 Sebastian Eschweilerが、RESTfulAPIとGraphQLの違いについて説明しています。

クライアント側のGraphQLは、GraphQLサーバーからのデータとインターフェイスして、次の機能を実行するクライアント側のインフラストラクチャです。

  • HTTPリクエストをすべて自分で作成しなくても、クエリを送信してデータを変更することでデータを管理します。 データの配管に費やす時間を減らし、実際のアプリケーションの構築により多くの時間を費やすことができます。
  • キャッシュの複雑さを管理します。 そのため、サードパーティの干渉なしにサーバーからフェッチされたデータを保存および取得でき、重複するリソースの再フェッチを簡単に回避できます。 したがって、2つのリソースが同じである場合を識別します。これは、複雑なアプリに最適です。
  • UIをOptimisticUIと一貫性のある状態に保ちます。これは、ミューテーションの結果(つまり、作成されたデータ)をシミュレートし、サーバーから応答を受信する前でもUIを更新する規則です。 サーバーから応答を受信すると、楽観的な結果は破棄され、実際の結果に置き換えられます。

クライアント側のGraphQLの詳細については、GraphQLの共同作成者やGraphQLRadioの他のクールな人々と1時間を割いてください。

Apolloクライアントとは何ですか?

Apollo Clientは、JavaScriptおよびネイティブプラットフォーム向けの相互運用可能で、非常に柔軟な、コミュニティ主導のGraphQLクライアントです。 その印象的な機能には、堅牢な状態管理ツール(Apollo Link)、ゼロ構成キャッシングシステム、データをフェッチするための宣言型アプローチ、実装が容易なページ付け、クライアント側アプリケーションのOptimisticUIが含まれます。

Apollo Clientは、サーバーからフェッチされたデータの状態だけでなく、クライアントでローカルに作成した状態も保存します。 したがって、APIデータとローカルデータの両方の状態を管理します。

また、競合することなく、Reduxなどの他の状態管理ツールと一緒にApolloClientを使用できることに注意することも重要です。 さらに、状態管理を、たとえばReduxからApollo Clientに移行することもできます(これはこの記事の範囲を超えています)。 最終的に、Apollo Clientの主な目的は、エンジニアがAPIでデータをシームレスにクエリできるようにすることです。

Apolloクライアントの機能

Apollo Clientは、最新の堅牢なアプリケーションを簡単に構築できる非常に便利な機能により、非常に多くのエンジニアや企業を獲得してきました。 次の機能が組み込まれています。

  • キャッシング
    Apollo Clientは、オンザフライでのキャッシュをサポートします。
  • 楽観的なUI
    Apollo Clientは、OptimisticUIをクールにサポートしています。 これは、操作の進行中に操作の最終状態(ミューテーション)を一時的に表示することを含みます。 操作が完了すると、実際のデータが楽観的なデータに置き換わります。
  • ページ付け
    Apollo Clientには、アプリケーションにページ付けを非常に簡単に実装できる機能が組み込まれています。 useQueryフックに付属するfetchMore関数を使用して、パッチで、または一度にデータのリストをフェッチする際の技術的な問題のほとんどを処理します。

この記事では、これらの機能の一部を見ていきます。

十分な理論。 手を汚すので、シートベルトを締めて、パンケーキと一緒にコーヒーを飲みましょう。

Webアプリの構築

このプロジェクトはスコットモスに触発されています。

シンプルなペットショップのウェブアプリを作成します。その機能は次のとおりです。

  • サーバー側からペットを取得します。
  • ペットの作成(名前、ペットの種類、画像の作成を含みます);
  • 楽観的なUIを使用する。
  • ページ付けを使用してデータをセグメント化します。

まず、リポジトリのクローンを作成し、 starterブランチがクローンしたものであることを確認します。

入門

  • Chrome用のApolloClient DeveloperTools拡張機能をインストールします。
  • コマンドラインインターフェイス(CLI)を使用して、複製されたリポジトリのディレクトリに移動し、コマンドを実行してすべての依存関係を取得します: npm install
  • コマンドnpm run appして、アプリを起動します。
  • ルートフォルダにいる間に、コマンドnpm run server 。 これにより、バックエンドサーバーが起動し、続行するときに使用します。

アプリは設定されたポートで開く必要があります。 私のはhttps://localhost:1234/です; あなたのものはおそらく別のものです。

すべてがうまく機能した場合、アプリは次のようになります。

クローンスターターブランチUI
クローンスターターブランチUI。 (大プレビュー)

表示するペットがいないことに気付くでしょう。 これは、そのような機能をまだ作成していないためです。

Apollo Client Developer Toolsを正しくインストールした場合は、開発者ツールを開いてトレイアイコンをクリックします。 「Apollo」と次のようなものが表示されます。

Apolloクライアント開発者ツール
Apolloクライアント開発者ツール。 (大プレビュー)

ReduxおよびReact開発者ツールと同様に、Apolloクライアント開発ツールを使用してクエリとミューテーションを記述およびテストします。 拡張機能には、GraphQLPlaygroundが付属しています。

ペットをフェッチする

ペットをフェッチする機能を追加しましょう。 client/src/client.jsに移動します。 Apolloクライアントを作成し、それをAPIにリンクし、デフォルトクライアントとしてエクスポートし、新しいクエリを作成します。

次のコードをコピーして、 client.jsに貼り付けます。

 import { ApolloClient } from 'apollo-client' import { InMemoryCache } from 'apollo-cache-inmemory' import { HttpLink } from 'apollo-link-http' const link = new HttpLink({ uri: 'https://localhost:4000/' }) const cache = new InMemoryCache() const client = new ApolloClient({ link, cache }) export default client

上記で起こっていることの説明は次のとおりです。

  • ApolloClient
    これは、アプリをラップし、HTTPとのインターフェースを取り、データをキャッシュし、UIを更新する関数になります。
  • InMemoryCache
    これは、アプリケーションのキャッシュの操作に役立つApolloClientの正規化されたデータストアです。
  • HttpLink
    これは、GraphQLリクエストの制御フローを変更し、GraphQLの結果をフェッチするための標準のネットワークインターフェースです。 これはミドルウェアとして機能し、リンクが起動されるたびにGraphQLサーバーから結果をフェッチします。 さらに、 Axioswindow.fetchなどの他のオプションの代わりにもなります。
  • HttpLinkのインスタンスに割り当てられるリンク変数を宣言します。 これは、 uriプロパティとサーバーへの値( https://localhost:4000/ )を取ります。
  • 次は、 InMemoryCacheの新しいインスタンスを保持するキャッシュ変数です。
  • クライアント変数もApolloClientのインスタンスを取り、 linkcacheをラップします。
  • 最後に、 clientをエクスポートして、アプリケーション全体で使用できるようにします。

これが実際に動作することを確認する前に、アプリ全体がApolloに公開されていること、アプリがサーバーからフェッチされたデータを受信できること、およびそのデータを変更できることを確認する必要があります。

これを実現するには、 client/src/index.jsにアクセスしてみましょう。

 import React from 'react' import ReactDOM from 'react-dom' import { BrowserRouter } from 'react-router-dom' import { ApolloProvider } from '@apollo/react-hooks' import App from './components/App' import client from './client' import './index.css' const Root = () => ( <BrowserRouter>
 <ApolloProvider client={client}> <App /> </ApolloProvider>
 </BrowserRouter> ); ReactDOM.render(<Root />, document.getElementById('app')) if (module.hot) { module.hot.accept() }

強調表示されたコードでわかるように、 AppコンポーネントをApolloProviderでラップし、クライアントを小道具としてclientに渡しました。 ApolloProviderは、ReactのContext.Providerに似ています。 Reactアプリをラップし、クライアントをコンテキストに配置します。これにより、コンポーネントツリーのどこからでもクライアントにアクセスできます。

サーバーからペットをフェッチするには、必要なフィールドを正確に要求するクエリを作成する必要があります。 client/src/pages/Pets.jsに移動し、次のコードをコピーして貼り付けます。

 import React, {useState} from 'react' import gql from 'graphql-tag' import { useQuery, useMutation } from '@apollo/react-hooks' import PetsList from '../components/PetsList' import NewPetModal from '../components/NewPetModal' import Loader from '../components/Loader'

const GET_PETS = gql` query getPets { pets { id name type img } } `;

export default function Pets () { const [modal, setModal] = useState(false)
 const { loading, error, data } = useQuery(GET_PETS); if (loading) return <Loader />; if (error) return <p>An error occured!</p>;


 const onSubmit = input => { setModal(false) } if (modal) { return <NewPetModal onSubmit={onSubmit} onCancel={() => setModal(false)} /> } return ( <div className="page pets-page"> <section> <div className="row betwee-xs middle-xs"> <div className="col-xs-10"> <h1>Pets</h1> </div> <div className="col-xs-2"> <button onClick={() => setModal(true)}>new pet</button> </div> </div> </section> <section>
 <PetsList pets={data.pets}/>
 </section> </div> ) }

数ビットのコードで、サーバーからペットをフェッチすることができます。

gqlとは何ですか?

GraphQLの操作は、通常、 graphql-tagとバッククォートで記述されたJSONオブジェクトであることに注意してください。

gqlタグは、GraphQLクエリ文字列をGraphQL AST(抽象構文木)に解析するJavaScriptテンプレートリテラルタグです。

  • クエリ操作
    サーバーからペットを取得するには、クエリ操作を実行する必要があります。
    • query操作を行っているため、名前を付ける前に操作のtypeを指定する必要がありました。
    • クエリの名前はGET_PETSです。 フィールド名にキャメルケースを使用するのはGraphQLの命名規則です。
    • 私たちのフィールドの名前はpetsです。 したがって、サーバーから必要な正確なフィールド(id, name, type, img)を指定します。
    • useQueryは、Apolloアプリケーションでクエリを実行するための基礎となるReactフックです。 Reactコンポーネントでクエリ操作を実行するために、 useQueryフックを呼び出します。これは、最初は@apollo/react-hooksからインポートされました。 次に、GraphQLクエリ文字列(この場合はGET_PETS )を渡します。
  • コンポーネントがレンダリングされると、 useQueryは、読み込み、エラー、およびデータのプロパティを含むオブジェクト応答をApolloクライアントから返します。 したがって、それらは構造化されていないため、UIをレンダリングするために使用できます。
  • useQueryは素晴らしいです。 async-awaitを含める必要はありません。 それはすでにバックグラウンドで処理されています。 かなりかっこいいですね。
    • loading
      このプロパティは、アプリケーションの読み込み状態を処理するのに役立ちます。 この例では、アプリケーションのロード中にLoaderコンポーネントを返します。 デフォルトでは、ロードはfalseです。
    • error
      念のため、このプロパティを使用して、発生する可能性のあるエラーを処理します。
    • data
      これには、サーバーからの実際のデータが含まれています。
    • 最後に、 PetsListコンポーネントで、 data.petsをオブジェクト値としてpetsを渡します。

この時点で、サーバーへのクエリは正常に完了しています。

アプリケーションを起動するには、次のコマンドを実行してみましょう。

  • クライアントアプリを起動します。 CLIでコマンドnpm run appします。
  • サーバーを起動します。 別のCLIでコマンドnpm run serverします。
VScode CLIは、クライアントとサーバーの両方を起動するようにパーティション化されています。
VScode CLIは、クライアントとサーバーの両方を起動するようにパーティション化されています。 (大プレビュー)

すべてがうまくいけば、次のように表示されます。

サーバーから照会されたペット。
サーバーから照会されたペット。

データの変更

Apollo Clientでのデータの変更またはデータの作成は、データのクエリとほとんど同じですが、わずかな変更があります。

まだclient/src/pages/Pets.jsにあり、強調表示されたコードをコピーして貼り付けましょう。

 .... const GET_PETS = gql` query getPets { pets { id name type img } } `;

const NEW_PETS = gql` mutation CreateAPet($newPet: NewPetInput!) { addPet(input: $newPet) { id name type img } } `;

 const Pets = () => { const [modal, setModal] = useState(false) const { loading, error, data } = useQuery(GET_PETS);
 const [createPet, newPet] = useMutation(NEW_PETS);
 const onSubmit = input => { setModal(false)
 createPet({ variables: { newPet: input } }); } if (loading || newPet.loading) return <Loader />; if (error || newPet.error) return <p>An error occured</p>;
  
 if (modal) { return <NewPetModal onSubmit={onSubmit} onCancel={() => setModal(false)} /> } return ( <div className="page pets-page"> <section> <div className="row betwee-xs middle-xs"> <div className="col-xs-10"> <h1>Pets</h1> </div> <div className="col-xs-2"> <button onClick={() => setModal(true)}>new pet</button> </div> </div> </section> <section> <PetsList pets={data.pets}/> </section> </div> ) } export default Pets

ミューテーションを作成するには、次の手順を実行します。

1. mutation

作成、更新、または削除するには、 mutation操作を実行する必要があります。 mutationテーション操作には、1つの引数を持つCreateAPet名があります。 この引数には、 NewPetInputタイプの$newPet変数があります。 ! 操作が必要であることを意味します。 したがって、タイプがNewPetInputであるnewPet変数を渡さない限り、GraphQLは操作を実行しません。

2. addPet

ミューmutation操作内にあるaddPet関数は、 inputの引数を取り、 $newPet変数に設定されます。 addPet関数で指定されたフィールドセットは、クエリのフィールドセットと同じである必要があります。 オペレーションのフィールドセットは次のとおりです。

  • id
  • name
  • type
  • img

useMutation

useMutation Reactフックは、Apolloアプリケーションでミューテーションを実行するための主要なAPIです。 データを変更する必要がある場合は、ReactコンポーネントでuseMutationを呼び出し、GraphQL文字列(この場合はNEW_PETS )を渡します。

コンポーネントがuseMutationをレンダリングすると、次のような配列でタプル(つまり、レコードを構成するデータの順序付けられたセット)が返されます。

  • ミューテーションを実行するためにいつでも呼び出すことができるmutate関数。
  • ミューテーションの実行の現在のステータスを表すフィールドを持つオブジェクト。

useMutationフックには、GraphQLミューテーション文字列(この場合はNEW_PETS )が渡されます。 タプルを非構造化しました。これは、データとオブジェクトフィールド( newPets )を変更する関数( createPet )です。

4. createPet

onSubmit関数では、 setModal状態の直後に、 createPetを定義しました。 この関数は、値が{ newPet: input }に設定されたオブジェクトプロパティを持つvariableを取ります。 inputは、フォームのさまざまな入力フィールド(名前、タイプなど)を表します。

これが完了すると、結果は次のようになります。

即時更新なしの突然変異
即時更新なしのミューテーション。

GIFを注意深く観察すると、ページが更新された場合にのみ、作成されたペットがすぐに表示されないことがわかります。 ただし、サーバー上で更新されています。

大きな問題は、なぜ私たちのペットがすぐに更新されないのかということです。 次のセクションで調べてみましょう。

Apolloクライアントでのキャッシュ

アプリが自動的に更新されない理由は、新しく作成されたデータがApolloクライアントのキャッシュデータと一致しないためです。 そのため、キャッシュから正確に何を更新する必要があるかについては競合があります。

簡単に言えば、複数のエントリ(ノード)を更新または削除するミューテーションを実行する場合、そのノードを参照するクエリを更新する責任があります。これにより、キャッシュされたデータが、ミューテーションがバックに対して行う変更と一致するように変更されます。終了データ。

キャッシュの同期を維持する

ミューテーション操作を実行するたびにキャッシュの同期を維持する方法はいくつかあります。

1つは、 refetchQueriesオブジェクトプロパティを使用して、ミューテーション後に一致するクエリを再フェッチすることです(最も簡単な方法)。

注:このメソッドを使用する場合、 refetchQueriesというcreatePet関数のオブジェクトプロパティを取得し、クエリの値を持つオブジェクトの配列を含みます: refetchQueries: [{ query: GET_PETS }]

このセクションでは、UIで作成したペットを更新するだけでなく、キャッシュを操作することに重点を置いているため、このメソッドは使用しません。

2番目のアプローチは、 update機能を使用することです。 Apollo Clientには、キャッシュデータの変更を支援するupdateヘルパー関数があり、ミューテーションがバックエンドデータに加える変更と同期します。 この関数を使用して、キャッシュの読み取りと書き込みを行うことができます。

キャッシュの更新

次の強調表示されたコードをコピーして、 client/src/pages/Pets.jsに貼り付けます。

 ...... const Pets = () => { const [modal, setModal] = useState(false) const { loading, error, data } = useQuery(GET_PETS);
 const [createPet, newPet] = useMutation(NEW_PETS, { update(cache, { data: { addPet } }) { const data = cache.readQuery({ query: GET_PETS }); cache.writeQuery({ query: GET_PETS, data: { pets: [addPet, ...data.pets] }, }); }, } );
 .....

update関数は2つの引数を受け取ります。

  • 最初の引数は、Apolloクライアントからのキャッシュです。
  • 2つ目は、サーバーからの正確なミューテーション応答です。 dataプロパティを分解し、ミューテーション( addPet )に設定します。

次に、関数を更新するには、更新する必要のあるクエリ(この場合はGET_PETSクエリ)を確認し、キャッシュを読み取る必要があります。

次に、読み取ったqueryに書き込む必要があります。これにより、クエリを更新しようとしていることがわかります。 これを行うには、 query dataプロパティを含み、値がquery操作( GET_PETS )に設定されているオブジェクトと、値がpetオブジェクトであり、 addPetミューテーションの配列とペットのデータ。

これらの手順を注意深く実行すると、ペットを作成するときにペットが自動的に更新されるのを確認できます。 変更点を見てみましょう。

ペットは即座に更新されます
ペットは即座に更新されます。

楽観的なUI

多くの人がローダーやスピナーの大ファンです。 ローダーを使用しても問題はありません。 ローダーが最良のオプションである完璧なユースケースがあります。 ローダーとスピナー、およびそれらの最良の使用例について説明しました。

ローダーとスピナーは確かにUIとUXの設計において重要な役割を果たしますが、OptimisticUIの登場は脚光を浴びています。

オプティミスティックUIとは何ですか?

オプティミスティックUIは、ミューテーション(作成されたデータ)の結果をシミュレートし、サーバーからの応答を受信する前にUIを更新する規則です。 サーバーから応答を受信すると、楽観的な結果は破棄され、実際の結果に置き換えられます。

結局のところ、楽観的なUIは、知覚されるパフォーマンスを管理し、状態の読み込みを回避する方法にすぎません。

Apollo Clientには、オプティミスティックUIを統合する非常に興味深い方法があります。 これにより、ミューテーション後にローカルキャッシュに書き込むことができる単純なフックが得られます。 それがどのように機能するか見てみましょう!

ステップ1

client/src/client.jsに移動し、強調表示されたコードのみを追加します。

 import { ApolloClient } from 'apollo-client' import { InMemoryCache } from 'apollo-cache-inmemory' import { HttpLink } from 'apollo-link-http'
import { setContext } from 'apollo-link-context' import { ApolloLink } from 'apollo-link' const http = new HttpLink({ uri: "https://localhost:4000/" }); const delay = setContext( request => new Promise((success, fail) => { setTimeout(() => { success() }, 800) }) ) const link = ApolloLink.from([ delay, http ])
const cache = new InMemoryCache() const client = new ApolloClient({ link, cache }) export default client

最初のステップは次のとおりです。

  • setContextapollo-link-contextからインポートします。 setContext関数はコールバック関数を受け取り、突然変異操作が実行されるときに遅延を作成するために、 setTimeout800msに設定されているpromiseを返します。
  • ApolloLink.fromメソッドは、 HTTPからのリンク(API)を表すネットワークアクティビティが遅延することを保証します。

ステップ2

次のステップは、OptimisticUIフックを使用することです。 client/src/pages/Pets.jsに戻り、強調表示されたコードのみを以下に追加します。

 ..... const Pets = () => { const [modal, setModal] = useState(false) const { loading, error, data } = useQuery(GET_PETS); const [createPet, newPet] = useMutation(NEW_PETS, { update(cache, { data: { addPet } }) { const data = cache.readQuery({ query: GET_PETS }); cache.writeQuery({ query: GET_PETS, data: { pets: [addPet, ...data.pets] }, }); }, } ); const onSubmit = input => { setModal(false) createPet({ variables: { newPet: input },
 optimisticResponse: { __typename: 'Mutation', addPet: { __typename: 'Pet', id: Math.floor(Math.random() * 10000 + ''), name: input.name, type: input.type, img: 'https://via.placeholder.com/200' } }
 }); } .....

optimisticResponseオブジェクトは、サーバーの応答を待つのではなく、ペットを作成したときにUIをすぐに更新する場合に使用されます。

上記のコードスニペットには、次のものが含まれます。

  • __typenameは、クエリされたエンティティのtypeをフェッチするためにApolloによってクエリに挿入されます。 これらのタイプは、 apollo-cacheでキャッシュする目的でidプロパティ(シンボル)を構築するためにApolloクライアントによって使用されます。 したがって、 __typenameはクエリ応答の有効なプロパティです。
  • ミューテーションは、 optimisticResponse__typenameとして設定されます。
  • 前に定義したように、ミューテーションの名前はaddPetで、 __typenamePetです。
  • 次に、楽観的な応答を更新する必要があるミューテーションのフィールドを示します。
    • id
      サーバーからのIDがわからないため、 Math.floorを使用してIDを作成しました。
    • name
      この値はinput.nameに設定されます。
    • type
      タイプの値はinput.typeです。
    • img
      これで、サーバーが画像を生成するため、プレースホルダーを使用してサーバーからの画像を模倣しました。

これは確かに長い道のりでした。 最後まで来たら、コーヒーを飲みながら椅子から休憩することを躊躇しないでください。

結果を見てみましょう。 このプロジェクトのサポートリポジトリはGitHubにあります。 クローンを作成して試してみてください。

ペットショップアプリの最終結果
私たちのアプリの最終結果。

結論

Optimistic UIやページ付けなどのApolloClientの驚くべき機能により、クライアント側のアプリの構築が現実のものになります。

ApolloクライアントはVue.jsやAngularなどの他のフレームワークと非常にうまく機能しますが、React開発者はApolloクライアントフックを持っているため、優れたアプリの構築を楽しむしかありません。

この記事では、表面を引っかいただけです。 Apollo Clientをマスターするには、絶え間ない練習が必要です。 したがって、先に進んでリポジトリのクローンを作成し、ページ付けを追加して、リポジトリが提供する他の機能を試してみてください。

以下のコメントセクションでフィードバックと経験を共有してください。 Twitterで進捗状況について話し合うこともできます。 乾杯!

参考文献

  • 「Client-SideGraphQLIn React」、Scott Moss、フロントエンドマスター
  • 「ドキュメント」、Apolloクライアント
  • 「Reactを使用した楽観的なUI」、Patryk Andrzejewski
  • 「楽観的なユーザーインターフェイスの真の嘘」、Smashing Magazine