ポケモンAPIを使ってGraphQLを今更覚える!
目的
有志の方が制作されているGraphQL のポケモン APIを使って
ポケモン検索アプリを作る過程でReact + TypeScript + GraphQLを覚える!
前述
本記事で作ったアプリはGitHubで公開しているので、よかったら記事を一緒に確認してください
事前準備
とりあえず React + TypeScript 環境を作る
npx create-react-app pokemon-graphql-practice --template typescript
たったのこれだけでReact + TypeScript環境が作成できる(神)
cd pokemon-graphql-practice
yarn start
不要なファイルは削除しましょう
- src/App.css
- src/App.text.tsx
- src/logo.svg
- src/setupTest.ts
App.tsxも不要な記述を消してとりあえずスッキリさせます
function App() {
return <div>pokemon-graphql-practice</div>;
}
- src/App.css
graphql-codegen で型定義を作る
GraphQLのSchema情報から型定義ファイルを作成してくれるすごいやつです
公式 Docsの Installation を参考にインストールする
yarn add graphql
yarn add -D @graphql-codegen/cli
yarn add -D @graphql-codegen/schema-ast // schema.graphqlを自動で作ってくれる!
セッティングを行う
yarn graphql-codegen init
色々聞かれるけどとりあえず全部Enterですっ飛ばしてOKです
script名を聞かれるのでそこだけ generate
と入力します
graphql-codegenを実行するのに yarn generate
で実行できるようになります
codegen.ymlが作成されるので、以下のように修正します
schema: "https://graphql-pokemon2.vercel.app" # GraphiQLを指定
generates:
./src/@types/types.d.ts:
plugins:
- "typescript"
- "typescript-operations" # fqueryやmutationで使用するOperationとかの型定義まで作ってくれる
./schema.graphql: # これ以下を記述するとschema.graphqlをDocumentから作ってくれる(スゴイ)
plugins:
- schema-ast
ここまで来たらあとは実行するのみです!
yarn generate
以下のファイルが作成されます
- ./src/@types/types.d.ts
- ./schema.graphql
Apollo をインストール
Apolloはフロントエンドから簡単にGraphQLを操作できるようにするライブラリです
yarn add @apollo/client
実装
ここまで来たらもう準備万端!
早速実装に入りましょう!!!
...と言いたいところですが、その前にgraphql-pokemonが提供しているAPI仕様を確認しましょう
Pikachu の番号/名前/画像 url を取得する Query を試す
GraphiQLを開き、右上のDocsを開きます(Docsの見方は省略します)
query:Queryをクリックすると、queryとpokemonsとpokemonのFieldsがあることがわかります
今回Pikachuのデータを取得したいので、pokemonを見てみると、pokemon(id: String,name: String): Pokemon
とありますね
String型のidまたはnameを引数として渡すことで、Pokemon型のレスポンスを返却することがわかります
ではPokemon型の詳細を見てみましょう
Pokemon型が持つFieldが表示されました
いろんなデータがありますがとりあえず今回必要なのは
- number
- name
- image
ですね
これを元に左側のエディタでQueryを作成してみましょう!
serachPikachu Queryを作りました
これを実行するとpikachuのデータが取れることがわかります
この"data"から始まるJSONが、クライアント側でQueryを実行した時のレスポンスとして返されます
GraphiQL超便利!!!
さてこれを実際にアプリで実行してみましょう!
Apollo の準備
src/graphql/client.tsを作成し、そこにApolloを使うためのクライアントを用意してあげます
ApolloClientを初期化します
import { ApolloClient, InMemoryCache } from "@apollo/client"
const apolloClient = new ApolloClient({
uri: "https://graphql-pokemon2.vercel.app/",
cache: new InMemoryCache(),
})
export { apolloClient }
uriにはGraphiQLを、cacheにはInMemoryCacheのインスタンスを渡します
cacheを設定することで同じクエリが発行された場合、自動的にキャッシュから結果を返却してくれるので、パフォーマンスがよくなるみたいです
とりあえず入れておいて損は無いかと思います
App.tsにApolloProviderと先ほど初期化したAppoloClientをインポートして、ApolloProviderでアプリ全体を囲ってあげます
import { ApolloProvider } from "@apollo/client";
import { apolloClient } from "./graphql/client";
function App() {
return (
<ApolloProvider client={apolloClient}>
{" "}
// 追加
<div>pokemon-graphql-practice</div>
</ApolloProvider> // 追加
);
}
これでこのApolloProviderに囲われてるコンポーネント内で、GraphQLのqueryやmutationを実行できるようになります
結果を表示しよう
GraphQL APIを実行して結果を表示するコンポーネントを作っていきます
src/graphql/queries.tsに先ほどGraphQLで確認したQueryを記載しておきましょう
gqlにテンプレートリテラルでQueryを書くといい感じにリクエストを作ってくれるみたいです
import gql from "graphql-tag"
export const searchPikachu = gql`
query searchPikachu {
pokemon(name: "pikachu") {
number
name
image
}
}
`
src/components/SearchResultField.tsxを作って、Queryを実行し結果を表示するコンポーネントを作ります
import { useQuery } from "@apollo/client"
import { Query } from "../@types/types"
import { searchPikachu } from "../graphql/queries"
const SearchResultField = () => {
const { loading, error, data } = useQuery<Query>(searchPikachu)
if (loading) return <>"Loading..."</>
if (error) return <>`Error! ${error.message}`</>
return (
<div>
<div>No: {data?.pokemon?.number}</div>
<div>Name: {data?.pokemon?.name}</div>
{data?.pokemon?.image ? (
<img src={data?.pokemon?.image} alt={data?.pokemon?.name ?? ""} />
) : (
<div>no image.</div>
)}
</div>
)
}
export { SearchResultField }
useQueryにジェネリクスとして、dataの型を渡すことができます
@typesからQueryをとってきて渡すことで、ちゃんとdataがQuery型だとVSCodeが判定してくれました
当初の想定ではdataはPokemon型になると思っていましたが、dataはGraphiQLでの結果のJSON(以下)のようにpokemonキーが一番上にあったので、dataはQuery型にしました
{
"data": {
"pokemon": {
...
}
}
}
簡単に説明していきます
useQuery
import { useQuery } from "@apollo/client";
const { loading, error, data } = useQuery<Query>(searchPikachu);
Apolloが用意してくれているHooksです
通信の状況に応じてloading,error,dataの値が変わるのでとても便利
ハンドリング
if (loading) return <>Loading...</>;
if (error) return <>Error! {error.message}</>;
loadingがtrueの場合はローディング中の文字、エラー発生時にはメッセージを返却するようにしています
<></>
これはフラグメントと行ってとりあえず囲みたい時に使います とても便利
データの表示
テストなので特別なハンドリングとかはせず、?.でごり押してます
imgだけsrcとaltにundifindを渡せなかったのでundifindの場合no image. が表示されるようにしました
return (
<div>
<div>No: {data?.pokemon?.number}</div>
<div>Name: {data?.pokemon?.name}</div>
{data?.pokemon?.image ? (
<img src={data?.pokemon?.image} alt={data?.pokemon?.name ?? ""} />
) : (
<div>no image.</div>
)}
</div>
);
ここまででPikachuのデータが表示されるようになりました
動的にポケモンの名前が動的に設定できるようにしよう
ここまでで結果を取得、表示ができました
あとはPikachu以外のポケモンも調べられるようにしたいので、検索フォームを作って動的にポケモンを検索できるようにします
まずはGraphiQLで変数を使ったQueryを作成してみます
変数を使った Query
Queryに変数を渡すには以下のように記載します
-2a9914945eb4.png)
左下のQUERY VARIABLESで変数にCharizardを入れて、それを左上のqueryで使用しています
検索結果が006 Charizardになったのがわかりますね
src/graphql/queries.tsに追加しておきましょう
export const searchPokemon = gql`
query searchPokemon($name: String) {
pokemon(name: $name) {
number
name
image
}
}
`
検索フォームを作成
src/components/SearchForm.tsxを作成し、検索フォームを作っていきます
import { FormEvent, useRef } from "react"
type PropType = {
setpokemonName: React.Dispatch<React.SetStateAction<string>>
}
const SearchForm: React.FC<PropType> = ({ setpokemonName }) => {
const ref = useRef<HTMLInputElement>(null)
const submitHandler = (e: FormEvent) => {
e.preventDefault()
if (ref !== null && ref.current !== null) {
setpokemonName(ref.current.value)
}
}
return (
<form onSubmit={submitHandler}>
<input type="text" ref={ref} placeholder="input Pokemon's name" />
<input type="submit" value="Search"></input>
</form>
)
}
export { SearchForm }
src/App.tsxもこれに伴い修正
function App() {
const [pokemonName, setpokemonName] = useState("")
return (
<> // 追加
<SearchForm setpokemonName={setpokemonName}></ SearchForm> // 変更
<ApolloProvider client={apolloClient}>
<SearchResultField></SearchResultField>
</ApolloProvider>
</> // 追加
)
}
Reactの解説記事では無いので詳細な説明は省きますが、
- input:textをuseRef Hooksで定義
- formのonSubmitでrefを参照し、valueを取得している
- App.tsxでuseStateで作成したpokemonNameのセッター?を渡して、onSubmitでそこにvalueを突っ込む
って感じのフォームにしています
Submitボタンを押した時、テキストボックスの値がApp.tsxのpokemonNameに、入るようになりました
検索条件を動的な値に設定する
さて、検索フォームから受け取ったpokemonNameを使って検索結果を表示しましょう
// ↓追加
type PropType = {
pokemonName: string
}
const SearchResultField: React.FC<PropType> = ({ pokemonName }) => { // 変更
const { loading, error, data } = useQuery<Query> (searchPokemon, { variables: { name: pokemonName } }) // 変更
if (!pokemonName) return <></> // 追加
if (loading) return <>Loading...</>
if (error) return <>Error! {error.message}</>
if (!data || !data.pokemon) return <>No Data.</> // 追加
// ↓変更
return (
<>
<div>No: {data.pokemon.number}</div>
<div>Name: {data.pokemon.name}</div>
{data.pokemon.image ? <img src={data.pokemon.image} alt={data.pokemon.name ?? ""} /> : <div>no image.</div>}
</>
)
}
useQuery<Query> (searchPokemon, { variables: { name: pokemonName } })
useQueryにsearchPokemonを第一引数で渡し、第二引数にvariablesを保持したObjectを渡しています
このvariablesには先ほどGraphiQLの左下でのQUERY VARIABLESで確認した物と同じ型のオブジェクトを渡します
useQueryは変数として渡したパラメータが変更された時に再度Queryを実行してくれるので、これで検索条件を可変にできました
最後にApp.tsxからpokemonNameを渡します
ついでにpokemonNameが存在しない場合は何も表示しないようにしました
<ApolloProvider client={apolloClient}>
{pokemonName && <SearchResultField pokemonName={pokemonName}> // 変更</SearchResultField>}
</ApolloProvider>
これでSubmitが押される度に検索結果が更新されるようになりました!
まとめ
React + TypeScript + GraphQLでポケモンの画像検索ができる簡易的なアプリを作成しました
大好きなポケモンでGraphQLを勉強出来て、graphql-pokemonの作者には感謝してもしきれません
が、欲を言うなら全ポケモンに対応して欲しい・・・あと日本語・・・
GraphQLの勉強のついでに作ってみましたが、GraphiQL最高すぎるし、graphql-code-generatorで型定義ファイルが作れるの天才だし、Apolloがとても使いやすくて、簡単でした
GraphiQLのお陰でREST APIより簡単にお試しできるので、とてもとっつきやすかったです
ただGraphQLじゃないと出来ないことっていうのがそこまで内容に見受けられました
最近流行りのgRPCとかだとRESTではできないことが出来たりするみたいです
とても気に入ったのでGraphQLには是非これから流行って欲しいですが、gRPCも勉強して流れに乗り遅れないようにしないといけないなと思いました・・・
今回はqueryしか使っていませんが(graphql-pokemonにはqueryしか用意されていません)
GitHub APIとかにはmutationもあるのでmutationの記事もそのうち書きたいです
バックエンドのGraphQLの書き方も勉強しないと・・・
参考
graphql-code-generator 公式 Docs
GraphQL Code Generator の使い方。〜GraphQL の Schema から TS の型定義を自動生成する〜
Discussion