👻

React + Graphqlで快適スキーマ駆動開発

2023/01/06に公開約4,900字

React+Graphqlの開発体験がすごくいいので、みんなに体験してもらいたくて、手軽なチュートリアル作りました。

1. graphqlモックサーバーを立ち上げよう

  1. create-react-appする
    npx create-react-app {プロジェクト名} --template typescript

  2. mockServer用のライブラリをインストール
    yarn add apollo-server graphql graphql-tools

  3. schema.graphqlを書いてみよう

type Book {
  id: ID!
  title: String!
  author: Author!
}

type Author {
  id: ID!
  name: String!
  age: Int
}

type Query {
  books: [Book!]
}

  1. mockServer用のファイル
const { ApolloServer } = require("apollo-server");
const { loadSchemaSync } = require("@graphql-tools/load");
const { GraphQLFileLoader } = require("@graphql-tools/graphql-file-loader");

const schema = loadSchemaSync("mockServer/schema.graphql", {
  loaders: [new GraphQLFileLoader()],
});

// サーバーを起動する
const server = new ApolloServer({ schema, mocks: true });

server.listen().then(({ url }) => {
  console.log(`🚀 Server ready at ${url}`);
});

package.jsonのscriptに
"mock:start": "node mockServer/index.js"
追加して
yarn mock:start

サーバーが立ち上がって
localhost:4000で実際にクエリ試したりできるはず!!

2. graphql-codegenで型を生成

yarn add -D @graphql-codegen/cli

yarn graphql-codegen init

色々質問されるので答えてくと、codegenの設定ファイル勝手に作ってくれる

overwrite: true
schema: "mockServer/schema.graphql"
generates:
  src/generated/graphql.tsx:
    plugins:
      - "typescript"
      - "typescript-operations"
      - "typescript-react-apollo"
    config:
      withComponent: false
      withHOC: false
      withHooks: true
  ./graphql.schema.json:
    plugins:
      - "introspection"

package.jsonのscriptに
"generate": "graphql-codegen --config codegen.yml"
して
yarn generate

自動で型ファイルが生成されるはず!

3. graphql-codegenでhooksを作成

yarn add @apollo/client

src/components/BookCard/graphql/fragment.ts

import { gql } from "@apollo/client";

export const BOOK_CARD_FRAGMENT = gql`
  fragment BookCard on Book {
    id
    title
    author {
      id
      name
    }
  }
`;

src/pages/Book/List/graphql/queries.ts

import { gql } from "@apollo/client";
import { BOOK_CARD_FRAGMENT } from "../../../../components/BookCard/graphql/fragment";

export const BOOK_LIST_QUERIES = gql`
  query GetBooks {
    books {
      ...BookCard
    }
  }
  ${BOOK_CARD_FRAGMENT}
`;
overwrite: true
schema: "mockServer/schema.graphql"
documents: "src/**/graphql/*.ts" # 追加
generates:
  src/generated/graphql.tsx:
    plugins:
      - "typescript"
      - "typescript-operations"
      - "typescript-react-apollo"
    config:
      withComponent: false
      withHOC: false
      withHooks: true
  ./graphql.schema.json:
    plugins:
      - "introspection"

再び
yarn generate

型ファイルが再度作り直されて
hooksが追加されるはず!!

4. 自動生成されたhooksの威力を体感しよう!

BookCardコンポーネントを作成

src/components/BookCard/index.tsx

import { FC } from "react";
import { BookCardFragment } from "../../generated/graphql";
import styles from "./style.module.css";

type Props = {
  book: BookCardFragment;
};
const BookCard: FC<Props> = ({ book }) => {
  return (
    <div className={styles.card}>
      <p>タイトル:{book.title}</p>
      <p>著者:{book.author.name}</p>
    </div>
  );
};

export default BookCard;

https://github.com/westlag114/react-graphql/blob/main/src/pages/Book/List/style.module.css

BookListPageを作成

src/pages/Book/List/index.tsx

import { FC } from "react";
import BookCard from "../../../components/BookCard";
import { useGetBooksQuery } from "../../../generated/graphql";
import styles from "./style.module.css";

const BookListPage: FC = () => {
  const { data, loading, error } = useGetBooksQuery();

  if (loading) return <p>...loading</p>;
  if (error) throw new Error(error.message);
  if (!data?.books) return <p>データがありません。</p>;

  return (
    <div className={styles.page}>
      <h1>本一覧</h1>
      {data.books.map((book) => (
        <div className={styles.cardWrapper} key={book.id}>
          <BookCard book={book} />
        </div>
      ))}
    </div>
  );
};

export default BookListPage;

https://github.com/westlag114/react-graphql/blob/main/src/pages/Book/List/style.module.css

src/App.tsx

import { FC } from "react";
import "./App.css";
import BookListPage from "./pages/Book/List";

const App: FC = () => {
  return (
    <div className="App">
      <BookListPage />
    </div>
  );
};

export default App;

src/index.tsx

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { ApolloClient, ApolloProvider, InMemoryCache } from "@apollo/client";

const client = new ApolloClient({
  uri: "http://localhost:4000",
  cache: new InMemoryCache(),
});

ReactDOM.render(
  <React.StrictMode>
    <ApolloProvider client={client}>
      <App />
    </ApolloProvider>
  </React.StrictMode>,
  document.getElementById("root")
);

無事データが表示されたら終了!
お疲れ様でした!!

Discussion

ログインするとコメントできます