TypeScript+Next.js+PokéAPIで作るポケモンジェネレーター【入門】
はじめに
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
を以下のように書き換えます。
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を状態変数で管理するようにします。
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
を更新して画像を変更するようにします。
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
を実装してコンソールで確認してみます。
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;
ページを読みこんで、Chrome
のDevTool
のConsole
にピカチュウの図鑑番号、名前、画像URLが表示されていたら成功です✌️
全国図鑑からランダムにデータを取得する
fetchPokemon
でURLの末尾にMath.floor(Math.random() * 905 + 1)
で生成した値を付け加えることで、ランダムにポケモンのデータを取得できます。※現時点(2023年1月)では9世代のポケモンのデータ取得ができていないので上限は905にしています。
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>
として表示させます。
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
として渡します。
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;
これで最初のポケモンもランダムで表示されるようになったので完成です🎉
参考リンク
Discussion