react-query と graphql-request で型指定された GraphQL


MagicBell のフロントエンドは React/TypeScript アプリケーションですが、バックエンドは Ruby で記述します.この記事では、クライアントをバックエンドに接続する方法と、API の重大な変更を防ぐ方法について説明します.

GraphQL コード ジェネレーター



まず、明白な選択を邪魔にならないようにしましょう. graphql-codegen .それは簡単です.このツールを使用すると、GraphQL スキーマに基づいて TypeScript 型を生成できます.型指定されたクエリ、ミューテーション、フラグメント、およびオブジェクト型を考えてみてください.

GraphQL スキーマに基づいて型が生成されるということは、バックエンド (GraphQL API) が重大な変更を導入したときに TypeScript が通知することを意味します.フロントエンドエンジニアとして、それが私の望みです!

GraphQL コード ジェネレーターは、指示があれば、完全に型指定された React フックを生成できますが、私は物事をシンプルに保つのが好きで、それによって TypedDocumentNode approach が好きです.この亜種は、使用している GraphQL クライアントを認識しません.つまり、react-apollo (または代替) に関連付けられていません.

それを起動して実行するには、いくつかの開発依存関係をインストールする必要があります.

npm i -D 
  @graphql-codegen/cli 
  @graphql-codegen/typed-document-node
  @graphql-codegen/typescript 
  @graphql-codegen/typescript-operations 
  @graphql-typed-document-node/core


そして今、よりエキサイティングな部分は、コード生成構成 ( codegen.yml ) です.コードに格納されたスキーマはありません.また、バックエンドが Ruby にあるため、graphql-codegen はバックエンド ソース ファイルから型を抽出することもできません.代わりに、GraphQL エンドポイントを提供します.セットアップは簡単ですが、1 つの欠点は、新しい型を生成するときにサーバーが実行されている必要があることです.

schema:
  - http://localhost:3000/graphql:
      headers:
        X-MAGICBELL-API-KEY: "${MAGICBELL_API_KEY}"
documents: "./app/javascript/src/graphql/**/*.graphql"
generates:
  ./app/javascript/src/graphql/generated.ts:
    plugins:
      - typescript
      - typescript-operations
      - typed-document-node


それでおしまい. Codegen はプロジェクト ルートから .env ファイルを読み取り、それを使用して API ヘッダーを設定します. documents は、GraphQL クエリが保存されるパスです.それらについては後で説明します.

Graphql クライアント



有名な @apollo/client は使用しません.ダッシュボードには大きすぎるからです.さらに、REST から GraphQL に移行しているため、しばらくの間、両方に対処する必要があります. React Query はキャッシュ管理に優れており、REST と GraphQL の両方に使用できるため、それを使用します.

小さいので swr の代わりに react-query を検討しましたが、必要な基礎がいくつか欠けています.ミューテーションを管理するための明確な状態インジケーターまたは (確実な) ソリューションを考えてください.

取得部分では、 graphql-request を使用します. 1 か月あたりのインストール数は apollo とほぼ同じですが、way smaller であり、未解決の問題はそれほど多くありません.

TypedDocumentNode と graphql-request



ここで「トリッキー」な部分ですが、生成された graphql-request を使用するには TypedDocumentNode が必要です.そのために、カスタム フックを作成しました.

import { useCallback } from 'react';
import { request } from 'graphql-request';
import { RequestDocument } from 'graphql-request/dist/types';
import { TypedDocumentNode } from '@graphql-typed-document-node/core';
import { useCurrentUser } from '../context';

export function useGraphqlRequest() {
  const { apiKey } = useCurrentUser();

  return useCallback(
    <TDocument = any, TVariables = Record<string, any>>(
      document: RequestDocument | TypedDocumentNode<TDocument, TVariables>,
      variables?: TVariables,
    ) =>
      request<TDocument, TVariables>('/graphql', document, variables, {
        'X-MAGICBELL-API-KEY': apiKey,
      }),
    [apiKey],
  );
}


それがすべての力になる魔法のソースです. graphql-request クライアントを返し、TypedDocumentNode を使用してクエリから型を推測します.また、いくつかのデフォルトを graphql-request に提供します.型注釈がなくても、これは、コード全体の複数の場所でオプション (ヘッダーなど) を設定できないようにする便利なフックになります.

クエリの作成



この設定により、バックエンドに自信を持ってクエリを実行する方法が得られます.新しいクエリを作成するには、*.graphql で宣言されたパスのどこかにある codegen.yml#documents ファイルに GraphQL 定義を書き込みます.

たとえば、この logs query :

query logMessage($id: ID!) {
  log(id: $id) {
    id
    createdAt
    user {
      firstName
      lastName
      email
    }
    notification {
      title
      content
      actionUrl
    }
  }
}


次に npx graphql-codegen を実行し、react-query を使用して作成したクエリ フックで生成された型を使用します.

import { useQuery } from 'react-query';
import { useGraphqlRequest } from './useGraphqlRequest';
import { LogMessageDocument, LogMessageQuery } from './generated';

type UseLogMessageOptions = {
  logId?: LogMessageQuery['log']['id'];
};

export function useLogMessage({ logId }: UseLogMessageOptions) {
  const request = useGraphqlRequest();

  return useQuery<LogMessageQuery['log']>(
    ['log-message', logId],
    () => request(LogMessageDocument, { id: logId }).then((x) => x.log),
    { enabled: logId != null },
  );
}


最後に、必要なデータを取得するために、コンポーネントでそのフックを使用します.

function LogDetails({ logId }: LogDetailsProps) {
  const { data, status } = useLogMessage({ logId });


以上です. data は完全に入力されています.バックエンド エンジニアが現在のフロントエンドと互換性のない変更を導入した場合や、そもそも存在しなかったデータを使用しているとします.その場合、TypeScript はプル リクエストに対して実行されるチェックの一部としてエラーをスローすることで通知します.

要約


graphql-codegen を使用して GraphQL クエリを入力し、react-query を使用してサーバー/クエリの状態を管理します. graphql-request は codegen と react-query の間の接着剤であり、必要に応じて、GraphQL 用に型指定された fetch です.この設定により、GraphQL クエリが MagicBell で壊れる可能性が減りました.