🐭

TypeScript+Next.js+PokéAPIで作るポケモンジェネレーター【入門】

2023/01/05に公開

はじめに

TypeScriptの勉強のために、ボタンを押すとポケモンのデータを取得して名前や画像を表示するだけの簡単なアプリケーションを作りました。勉強も兼ねて、やったことや学んだことをまとめていこうと思います。これからTypeScriptやReact、Next.jsを学ぶ人のちょっとした助けになれば嬉しいです。

作ったもの

成果物は http://random-pokemon-liart.vercel.app/ から実際に動かせます。
また、ソースコードはt4ichi/random-pokemonで確認できます。

あると良い知識

・コマンドラインの簡単な操作方法
・HTML、JavaScript、Reactの基礎知識

TypeScriptとは

ざっくりいうと「JavaScriptの上位互換」です。型定義が使えるため安全性が高く、大規模な開発でもうまく機能するように設計されています。TypeScriptで書いたコードをコンパイルすることでJavaScriptに変換される仕組みになっているため、JavaScriptとの互換性も高いです。

Next.jsとは

React(Webサイト上のUIを構築するためのライブラリ)をベースにしたフロントエンドフレームワークです。Reactの機能性を保ちつつ、サーバ機能や静的なWebサイトの生成といったWebアプリ開発で快適なウェブサイトを構築できたりする機能が備わっています。

環境構築

すでにインストール済みの方はここのステップはスキップして大丈夫です。

Node.jsのインストール

TypeScriptのコンパイラを動かすためにNode.jsのインストールが必要になるようです。
そのため、ターミナルからHomebrewを使ってインストールします。

brew install node@16

インストールが完了したらパスを追加します。

echo 'export PATH="/usr/local/opt/node@16/bin:$PATH"' >> ~/.zshrc

パスを追加したらターミナルを再起動し、以下のコマンドでバージョンが表示されるかを確認してみます。バージョンを表示できたらインストール完了です。

node -v
# v16.19.0

※バージョンはv16.19.0と異なっていても問題ないです。

Yarnのインストール

パッケージ管理ツールとしてYarnを利用します。主にyarn devでサーバーを起動する時に使います。以下のコマンドを使ってインストールします。

npm install -g yarn

バージョンが確認できたら完了です。

yarn -v
# 1.22.19

Next.jsのセットアップ

最初に以下のコマンドを使ってプロジェクトを作成します。

yarn create next-app --example with-typescript random-pokemon

プロジェクトが作成できたら、アプリケーションを起動してみます。

cd random-pokemon
yarn dev

アプリケーションの起動したら、ターミナルに表示されているURL(http://localhost:3000)にブラウザでアクセスし、以下のようなページが表示されていたら完了です🥳

実装

画像を表示する

最初はAPIでデータを取得せずに静的に画像を表示します。
index.tsxを以下のように書き換えます。

pages/index.tsx
const IndexPage = () => {
  return <img src="https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/887.png" />;
};

export default IndexPage;

画像の表示ができました👍URLの887の箇所を書き換えることで別のポケモンを表示することもできます。番号はポケモン全国図鑑順に対応しているようです。

画像URLを状態で管理

まず、画像を切り替えるためにpokemonImageUrl変数を作り、srcで指定するURLを状態変数で管理するようにします。

pages/index.tsx
import { useState } from "react";

const IndexPage = () => {
  const [pokemonImageUrl, setPokemonImageUrl] = useState(
    "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/887.png"
  );

  return <img src={pokemonImageUrl} />;
};

export default IndexPage;

ボタンクリックでランダムに画像を表示する

配列で保持した画像のURLをランダムに返すrandomPokemonImage関数を実装します。
ボタンを新しく追加して、ボタンがクリックされた時にsetPokemonImageUrl(randomPokemonImage())pokemonImageUrlを更新して画像を変更するようにします。

pages/index.tsx
import { useState } from "react";

const pokemonImages: string[] = [
  "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/149.png",
  "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/445.png",
  "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/887.png",
];

const randomPokemonImage = (): string =>{
  const index = Math.floor(Math.random() * pokemonImages.length);
  return pokemonImages[index];
};

const IndexPage = () => {
  const [pokemonImageUrl, setPokemonImageUrl] = useState(
    "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/887.png"
  );

  const handleClick = () => {
    setPokemonImageUrl(randomPokemonImage);
  };

  return (
    <div>
      <button onClick={handleClick}>チェンジ</button>
      <img src={pokemonImageUrl}/>  
    </div>
    
  );
};

export default IndexPage;

ボタンをクリックしてランダムにポケモンが切り替わるようになりました😎

これからAPIを使ってデータを更新できるようにします。

PokeAPIについて

PokeAPIはポケモンのデータを取得できるAPIです。
試しにピカチュウのデータである https://pokeapi.co/api/v2/pokemon/25 にアクセスすると名前や図鑑番号など約10,000行のデータが取得できます。このデータを一部だけ抜粋すると以下ようなデータ構造になっています。

{
  "name": "pikachu",
  "id": 25,
  "sprites": {
    "back_default": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/25.png",
    "back_female": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/female/25.png",
    "back_shiny": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/shiny/25.png",
    "back_shiny_female": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/shiny/female/25.png",
    "front_default": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/25.png",
    "front_female": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/female/25.png",
    "front_shiny": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/shiny/25.png",
    "front_shiny_female": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/shiny/female/25.png",
    "other": {
      "dream_world": {
        "front_default": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/dream-world/25.svg",
        "front_female": null
      }
    }
  }
}

ここから欲しいデータを取得して作っていきます。

PokeAPIリクエストして画像を取得

最初にAPIリクエストで画像を取得するfetchPokemonを実装してコンソールで確認してみます。

pages/index.tsx
import { useState } from "react";

const pokemonImages: string[] = [
  "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/149.png",
  "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/445.png",
  "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/887.png",
];

const randomPokemonImage = (): string =>{
  const index = Math.floor(Math.random() * pokemonImages.length);
  return pokemonImages[index];
};

const fetchPokemon = async () =>{
  const res = await fetch("https://pokeapi.co/api/v2/pokemon/25");
  const result = await res.json();
  return result;
};
fetchPokemon().then((pokemon) => {
  console.log(`図鑑番号: ${pokemon['id']}`);
  console.log(`名前: ${pokemon['name']}`);
  console.log(`画像URL: ${pokemon['sprites']['front_default']}`);
});

const IndexPage = () => {
  const [pokemonImageUrl, setPokemonImageUrl] = useState(
    "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/887.png"
  );

  const handleClick = () => {
    setPokemonImageUrl(randomPokemonImage);
  };

  return (
    <div>
      <button onClick={handleClick}>チェンジ</button>
      <img src={pokemonImageUrl}/>  
    </div>
  );
};

export default IndexPage;

ページを読みこんで、ChromeDevToolConsoleにピカチュウの図鑑番号、名前、画像URLが表示されていたら成功です✌️

全国図鑑からランダムにデータを取得する

fetchPokemonでURLの末尾にMath.floor(Math.random() * 905 + 1)で生成した値を付け加えることで、ランダムにポケモンのデータを取得できます。※現時点(2023年1月)では9世代のポケモンのデータ取得ができていないので上限は905にしています。

pages/index.tsx
const fetchPokemon = async () =>{
  const index = Math.floor(Math.random() * 905 + 1);
  const res = await fetch("https://pokeapi.co/api/v2/pokemon/" + index);
  const result = await res.json();
  return result;
};

先ほどと同様にページを更新しConsoleをみると、ランダムにポケモンの情報が取得できていることが確認できます。

ボタンをクリックした時にデータを更新する

APIリクエストでデータを取得することができるようになったので、ボタンをクリックしたら画面を更新できるようにします。

IndexPageでそれぞれポケモンのデータをstate受け取るようにし、handleClickでボタンを押したときsetPokemonID(pokemon['id'])でデータを更新します。
最後にreturn<p>{pokemonID} {pokemonName}</p>として表示させます。

pages/index.tsx
import { useState } from "react";

const fetchPokemon = async () =>{
  const index = Math.floor(Math.random() * 905 + 1);
  const res = await fetch("https://pokeapi.co/api/v2/pokemon/" + index);
  const result = await res.json();
  return result;
};
fetchPokemon().then((pokemon) => {
  console.log(`図鑑番号: ${pokemon['id']}`);
  console.log(`名前: ${pokemon['name']}`);
  console.log(`画像URL: ${pokemon['sprites']['front_default']}`);
});

const IndexPage = () => {
  const [pokemonID,setPokemonID] = useState(
    887
  );  
  const [pokemonName,setPokemonName] = useState(
    "dragapult"
  );
  const [pokemonImageUrl, setPokemonImageUrl] = useState(
    "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/887.png"
  );

  const handleClick = async () => {
    const pokemon = await fetchPokemon();
    setPokemonID(pokemon['id']);
    setPokemonName(pokemon['name']);
    setPokemonImageUrl(pokemon['sprites']['front_default']);
  };

  return (
    <div>
      <button onClick={handleClick}>チェンジ</button>
      <div>
        <img src={pokemonImageUrl}/>  
        <p>{pokemonID} {pokemonName}</p>
      </div>
      
    </div>
  );
};

export default IndexPage;

pokemonImages配列とrandomPokemonImage関数は使わないので削除しました。

ボタンをクリックすると各データがランダムに表示できるようになりました。

最初のポケモンもランダムに表示されるようにする

ここまでは最初のページを読み込むときに固定のポケモンを表示していましたが、最初のポケモンもランダムで表示するようにします。

そのためにはgetServerSidePropsというメソッドを使います。まず、IndexPagePropsというデータを渡すための型をそれぞれid: numberのように定義します。そしてIndexPage : NextPage<IndexPageProps>としてIndexPagePropsからデータを受け取るようにします。

最後に、getServerSidePropsでポケモンのデータを取得して、propsとして渡します。

pages/index.tsx
import { useState } from "react";
import { GetServerSideProps, NextPage } from "next";

const fetchPokemon = async () =>{
  const index = Math.floor(Math.random() * 905 + 1);
  const res = await fetch("https://pokeapi.co/api/v2/pokemon/" + index);
  const result = await res.json();
  return result;
};
fetchPokemon().then((pokemon) => {
  console.log(`図鑑番号: ${pokemon['id']}`);
  console.log(`名前: ${pokemon['name']}`);
  console.log(`画像URL: ${pokemon['sprites']['front_default']}`);
});

interface IndexPageProps{
  id: number;
  name: string;
  front_image: string;
}

const IndexPage : NextPage<IndexPageProps> = (props: IndexPageProps) => {
  const [pokemonID,setPokemonID] = useState(
    props.id
  );  
  const [pokemonName,setPokemonName] = useState(
    props.name
  );
  const [pokemonImageUrl, setPokemonImageUrl] = useState(
    props.front_image
  );

  const handleClick = async () => {
    const pokemon = await fetchPokemon();
    setPokemonID(pokemon['id']);
    setPokemonName(pokemon['name']);
    setPokemonImageUrl(pokemon['sprites']['front_default']);
  };

  return (
    <div>
      <button onClick={handleClick}>チェンジ</button>
      <div>
        <img src={pokemonImageUrl}/>  
        <p>{pokemonID} {pokemonName}</p>
      </div>
    </div>
  );
};

export const getServerSideProps: GetServerSideProps = async () => {
  const pokemon = await fetchPokemon();
  return {
    props: {
      id: pokemon['id'],
      name: pokemon['name'],
      front_image: pokemon['sprites']['front_default'],
    },
  };
};

export default IndexPage;


これで最初のポケモンもランダムで表示されるようになったので完成です🎉

参考リンク

https://typescriptbook.jp/tutorials/nextjs
https://pokeapi.co/

Discussion