# GraphQL Client ## SHARE `graphql-request > @apollo/client > react-relay = urql` ![](https://i.imgur.com/7Z5CL4F.png) https://npmtrends.com/@apollo/client-vs-graphql-request-vs-react-relay-vs-urql ## SIZE(MINIFIED + GZIPPED) `react-relay > @apollo/client > urql > graphql-request` * graphql-request: 7.6kB * https://bundlephobia.com/package/graphql-request@4.3.0 * @apollo/client: 40kB * https://bundlephobia.com/package/@apollo/client@3.6.9 * react-relay: 55.6kB * https://bundlephobia.com/package/react-relay@14.1.0 * urql: 8.5kB * https://bundlephobia.com/package/urql@2.2.3 ## Functions * graphql-request * 最もシンプルで軽量なGraphQL クライアント * https://www.npmjs.com/package/graphql-request * @apollo/client * Apollo Clientは JavaScript 用の包括的な状態管理ライブラリであり、GraphQL を使用してローカル データとリモート データの両方を管理できます。これを使用して、UI を自動的に更新しながら、アプリケーション データをフェッチ、キャッシュ、および変更します。 * https://www.apollographql.com/docs/react/ * Suspense対応が遅い(今もされてない?) * react-relay * Relay は、React アプリケーションで GraphQL データを取得および管理するための JavaScript フレームワークであり、保守性、タイプ セーフ、およびランタイム パフォーマンスを重視しています。 * Relay は、宣言型データ フェッチと静的ビルド ステップを組み合わせることでこれを実現します。宣言型のデータ取得では、コンポーネントが必要なデータを宣言し、Relay がデータを効率的に取得する方法を見つけ出します。静的ビルド ステップ中に、Relay はクエリを検証して最適化し、アーティファクトを事前に計算して実行時のパフォーマンスを向上させます。 * https://relay.dev/docs/ * Reactと作ってる会社一緒なので、Suspense対応とかが早い * urql * urqlは高度にカスタマイズ可能で汎用性の高い GraphQL クライアントであり、成長に合わせて正規化されたキャッシュなどの機能を追加できます。これは、GraphQL の初心者にとって使いやすく、拡張可能で、動的な単一アプリ アプリケーションと高度にカスタマイズされた GraphQL インフラストラクチャをサポートするように構築されています。 * https://formidable.com/open-source/urql/docs/ * Suspense対応済み ## in short * graphql-request * シェアが高く、最もシンプルなGraphQL Client * @apollo/client * シェアが高く、状態管理機能をメインとしたGraphQL Client * react-relay * 最も高機能なGraphQL Client * urql * キャッシュ機構を備えた拡張性のあるGraphQL Client * JS及びReactのコンサル企業であるFormidableによって開発された ## How to use ### graphql-request https://github.com/prisma-labs/graphql-request #### codegen ```yaml generated/graphql-request.ts: plugins: - typescript - typescript-operations - typescript-graphql-request ``` https://www.graphql-code-generator.com/plugins/typescript/typescript-graphql-request #### client ```typescript import { GraphQLClient } from 'graphql-request' import { getSdk } from '../schema/generated/graphql-request' const client = new GraphQLClient("http://localhost:9002/graphql") const sdk = getSdk(client) sdk.books() ``` #### キャッシュ機構を使いたいなら、SRWとセットで使う ##### コードサンプル `pages/graphql-request.tsx` ```typescript import type { NextPage } from 'next' import styles from '@styles/Home.module.css' import dynamic from 'next/dynamic' const BooksFetch = dynamic( () => { return import('@atoms/graphql-request-books') }, { ssr: false, } ) const Home: NextPage = () => { return ( <div className={styles.container}> <main className={styles.main}> <BooksFetch /> </main> </div> ) } export default Home ``` `@atoms/graphql-request-books/index.tsx` ```typescript import { memo, Suspense } from 'react' import useBooks from './hooks' function sleep(ms: number) { return new Promise((resolve) => { setTimeout(resolve, ms) }) } const SometimesSuspend = () => { if (Math.random() < 0.5) { console.log('sleep..') // eslint-disable-next-line @typescript-eslint/no-throw-literal throw sleep(1000) } return <p>Hello, world!</p> } const BooksComponent = () => { const { data } = useBooks() return ( <div> <SometimesSuspend /> {data?.books ?.map((v) => { return `{${v?.author}, ${v?.title}}` }) .toString()} </div> ) } // eslint-disable-next-line react/display-name const BooksFetch = memo(() => { return ( <Suspense fallback={<div>loading...</div>}> <BooksComponent /> </Suspense> ) }) export default BooksFetch ``` `@atoms/graphql-request-books/hooks.tsx` ```typescript import useSWR from 'swr' import { GraphQLClient } from 'graphql-request' import { getSdk, BooksQuery, BooksDocument } from '../../../schema/generated/typescript/graphql-request' const useBooks = () => { const client = new GraphQLClient('http://localhost:9002/graphql') const sdk = getSdk(client) const { data, error } = useSWR<BooksQuery>([BooksDocument], sdk.books, { suspense: true, revalidateOnFocus: false, revalidateIfStale: false, revalidateOnReconnect: false, }) return { data, error } } export default useBooks ``` ##### Suspenseの使い方 https://zenn.dev/uhyo/books/react-concurrent-handson ##### SWRの使い方 シンプルな `graphql-client` とセットで使い、キャッシュまわりはSWRで管理する SWRとは? https://zenn.dev/yukikoma/articles/17adad7fedd5af SWRのオプション https://swr.vercel.app/docs/options ##### はまりどころ Next.jsでSuspense利用時に、Hydration failedで表示がエラーになってしまう 対処法 https://qiita.com/horihorikei/items/4c467009a596d24949b2 ### urql https://github.com/FormidableLabs/urql #### codegen ```yaml generated/urql.tsx: plugins: - typescript - typescript-operations - typescript-urql ``` https://www.graphql-code-generator.com/plugins/typescript/typescript-urql #### client ```typescript import { withUrqlClient } from 'next-urql' import { useBooksQuery } from '../schema/generated/urql' const Home = () => { const [res] = useBooksQuery() console.log(res) ... } export default withUrqlClient(() => { return { url: 'http://localhost:9002/graphql', } })(Home) ``` 公式 https://formidable.com/open-source/urql/docs/advanced/server-side-rendering/#nextjs サンプル https://github.com/FormidableLabs/urql/tree/main/examples/with-next オススメしている記事 https://zenn.dev/adwd/articles/f4c5c5120467bb #### キャッシュ機構 ``` すべてのquery と variable の組み合わせはハッシュとして保持され、 レスポンスと共にキャッシュされるため、 再度同じリクエうとが投げられるとリクエストは投げられずキャッシュされます。 document caching では、 キャッシュに残ってる query に対しての mutation を実行した時、 値が更新されると仮定して現在の query のキャッシュを無効化します。 ``` この記事で掘り下げている https://zenn.dev/takurinton/articles/ee14cdd8a1630c https://dev.takurinton.com/tech/graphql/graphcache.html#document-caching-%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6 #### Suspenseの使い方 `pages/urql.tsx` ```typescript import type { NextPage } from 'next' import { withUrqlClient } from 'next-urql' import styles from '@styles/Home.module.css' import dynamic from 'next/dynamic' const BooksFetch = dynamic( () => { return import('@/components/atoms/urql-books') }, { ssr: false, } ) const Home: NextPage = () => { return ( <div className={styles.container}> <main className={styles.main}> <BooksFetch /> </main> </div> ) } export default withUrqlClient(() => { return { url: 'http://localhost:9002/graphql', suspense: true, } })(Home) ``` `@/components/atoms/urql-books.tsx` ```typescript import { Suspense } from 'react' import { useBooksQuery } from '../../../schema/generated/urql' function sleep(ms: number) { return new Promise((resolve) => { setTimeout(resolve, ms) }) } const SometimesSuspend = () => { if (Math.random() < 0.5) { console.log('sleep..') // eslint-disable-next-line @typescript-eslint/no-throw-literal throw sleep(1000) } return <p>Hello, world!</p> } const BooksComponent = () => { const [res] = useBooksQuery() return ( <div> <SometimesSuspend /> {res.data?.books ?.map((v) => { return `{${v?.author}, ${v?.title}}` }) .toString()} </div> ) } // eslint-disable-next-line react/display-name const BooksFetch = () => { return ( <Suspense fallback={<div>loading...</div>}> <BooksComponent /> </Suspense> ) } export default BooksFetch ``` ### Apollo Client https://www.apollographql.com/docs/react/ #### codegen ```yaml generated/typescript/react-apollo.ts: plugins: - typescript - typescript-operations - typescript-react-apollo ``` https://www.graphql-code-generator.com/plugins/typescript/typescript-react-apollo #### キャッシュ機構 クエリの結果をインメモリのキャッシュに自動で保存してくれる https://qiita.com/tokio_dev/items/945670f580c5cf507d97 https://zenn.dev/kazu777/articles/b64935ea7d6fee ##### InMemoryCache について **前提** ↓ を実行することで、クエリが実行され `client.cache` にデータが溜まっていく ``` const query = gql` query books { books { title } }` client.query({ query }) ``` ↓ `client.cache.data.data`の中身(データにIDが含まれていない場合) ``` { "ROOT_QUERY": { "__typename": "Query", "books": [ { "__typename": "Book", "title": "Harry Potter and the Chamber of Secrets" }, { "__typename": "Book", "title": "Jurassic Park : ENV[undefined]" } ] } } ``` データにIDが含まれている場合はこうなる ``` cache.writeQuery({ query, data: { books: [{ id: '1111', title: 'john', __typename: 'Book' }] } }) ``` ``` { "Book:1111": { "__typename": "Book", "title": "john", "id": "1111" }, "ROOT_QUERY": { "__typename": "Query", "books": [ { "__ref": "Book:1111" } ] } } ``` → 勝手に` __typename:ID` というフォーマットがキーになる ##### Apollo Clientのキャッシュ操作 5種のメソッドが生えている * readQuery ↓Queryを叩いた後、readQueryを実行することで値が取れる ``` client.readQuery({ query }) ``` ``` { "books": [ { "__typename": "Book", "title": "john" } ] } ``` Queryから得られる値は固定なので、booksのキャッシュデータが返ってる IDとか引数指定されてたらどうなるんだ? ``` // ID指定 const query = gql` query books($id: ID!) { books(id: $id) { title } }` // ID指定で実行 cache.writeQuery({ query, variables: {id: 3}, data: { books: [{ id: '1111', title: 'john', __typename: 'Book' }] } }) // ID指定でキャッシュ取得(ID指定しないとnull client.readQuery({ query, variables: {id: 3}, }); ``` ↓ キャッシュの中身 ``` { "Book:1111": { "__typename": "Book", "title": "john", "id": "1111" }, "ROOT_QUERY": { "__typename": "Query", "books({\"id\":3})": [ { "__ref": "Book:1111" } ] } } ``` * writeQuery キャッシュに書き込むことが出来る * updateQuery キャッシュを更新することが出来る(試してないので、たぶん * readFragment fragmentとキャッシュのキーを指定することで、部分的に値を取れる ``` const fragment = gql` fragment hoge on Book { title } client.readFragment({ id: 'Book:1111', fragment: fragment, }) ``` ``` { "__typename": "Book", "title": "john" } ``` * writeFragment 部分的に書き込める感じだと思う(試してないので、たぶん 参考 https://www.apollographql.com/docs/react/api/cache/InMemoryCache https://tech.jxpress.net/entry/understand-apollo-cache https://www.apollographql.com/docs/react/caching/overview/ ツール https://chrome.google.com/webstore/detail/apollo-client-devtools/jdkknkkbebbapilgoeccciglkfbmbnfm ### relay https://relay.dev/ ここまでリッチなものは今いま必要ないので割愛 # 状態管理 ## Recoil Facebook製のReact用状態管理ライブラリ Context APIのデメリットとReduxの使いづらさを解消するために開発された ![](https://i.imgur.com/BZyE9Lf.png) ![](https://i.imgur.com/FxodL7W.png) https://ics.media/entry/210224/ # GraphQL Client と コンポーネントの接続 ## Fragment Colocation コンポーネントとセットで、 そのコンポーネントが必要なデータを Fragment で宣言する、 React の宣言的 UI に合わせた宣言的データ取得(declarative data-fetching)という考え方 (例) ```typescript import type { NextPage } from 'next' import { GraphQLClient } from 'graphql-request' import SampleBook from '@atoms/SampleBook' import { getSdk, BooksQuery } from '@graphql-client' type Props = { book: BooksQuery } const SsrBook: NextPage<Props> = ({ book }) => { return ( <div> <main> <SampleBook fragment={book.book1} /> <SampleBook fragment={book.book2} /> <SampleBook fragment={book.book3} /> </main> </div> ) } SsrBook.getInitialProps = async (): Promise<Props> => { const client = new GraphQLClient('http://localhost:9002/graphql') const sdk = getSdk(client) const book = await sdk.books() return { book } } export default SsrBook ``` https://zenn.dev/izumin/scraps/46aba5181afe82 https://tech.uzabase.com/entry/2022/06/30/164016 ## 使い方 https://maasaablog.com/development/react/3226/ https://nulab.com/ja/blog/nulab/recoil-example/ # reference https://user-first.ikyu.co.jp/entry/2022/07/01/121325 https://nulab.com/ja/blog/nulab/graphql-apollo-relay-urql/ https://zenn.dev/seya/scraps/9d64f2e9cae500