目的
有志の方が制作されている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 に変数を渡すには以下のように記載します
左下の 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 の型定義を自動生成する〜