フロントエンド学習記No.3 PokeAPIでポケモンのデータを取得してみる

### はじめに
第3回目はPokeAPIを用いてポケモンのデータを取得し、表示することに挑戦したいと思います。

### 目標物
IDや名前等を入れると、それに合致するポケモンの情報を表示するといったシンプルな機能を実装したいと思います。


### APIとは？

https://ja.wikipedia.org/wiki/%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%95%E3%82%A7%E3%83%BC%E3%82%B9

Application Programming Interface(アプリケーションプログラミングインターフェース)の略です。

> 広義ではソフトウェアコンポーネント同士が互いに情報をやりとりするのに使用するインタフェースの仕様である。

説明が難しいのですが、異なるソフトウェア同士のやり取りを橋渡しする機能と自分は認識しています。
PokeAPIはポケモンの情報にアクセスするためのインターフェース（エンドポイント）を提供し、そのエンドポイントにリクエストを送ることで、様々な情報を取得できるというわけですね。

### PokeAPIについて

https://pokeapi.co/

ポケモンに関する様々な情報を提供するAPIになります。
例えば
こちらはわざマシンの情報を取得するためのエンドポイントっぽいですね。
![スクリーンショット 2023-12-31 23.37.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3301763/bc802da1-7175-c7f6-6daa-1faf16322596.png)
ポケモンの情報を取得するエンドポイント
![スクリーンショット 2023-12-31 23.42.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3301763/cf134dbb-18b7-559c-cdde-549ccd5f4561.png)


GETというのはHTTPリクエストメソッドですね。
URIが指し示す場所にある情報を「ください！」とwebサーバーにリクエストを送るためのメソッドです。
これを行うことでその場所にある情報がrequestとして返ってきます。
この情報をブラウザ上に上手く表示することができれば良さそうです。


https://developer.mozilla.org/ja/docs/Web/HTTP/Methods

### 試しにエンドポイントにリクエストを送ってみる
POSTMANという手軽にリクエストを送ることができるソフトを使用します。

```
https://pokeapi.co/api/v2/ability/{id or name}/
```
id or name
と書いてあるので適当に1を入れてGETを行う。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3301763/10b7c499-602e-43d4-d417-a93aeec5fa8e.png)

たくさんの情報がresponseとして返ってきています。
sprites
というのが画像情報みたいですね。
front_defaultというURLにアクセスしてみると
![スクリーンショット 2023-12-31 23.54.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3301763/f2f8ebbc-e4ff-b9d2-2550-6f3883f74eb0.png)
可愛いフシギダネの画像が出てきました！

このエンドポイントをベースにし、ユーザーが入力したIDを受け取ってリクエストを送れるようにすれば、目標物を作成することができそうです。

### 実際に作ったものがこちら

https://github.com/rikuya98/ts_pokeApi?tab=readme-ov-file

### ソースコード
```app.ts
const baseUri: string = "https://pokeapi.co/api/v2/pokemon/"
const nameUri: string = "https://pokeapi.co/api/v2/pokemon-species/"
const searchButton = document.querySelector('#search-btn')!;
const randomButton = document.querySelector('#random-btn')!;
const imgFrom: HTMLImageElement = document.querySelector('#pokemon-image')!;
const POKEMON_MAX_ID: number = 1000;
const POKEMON_MIN_ID: number = 1;

interface PokemonLanguageEntry {
    language: {
      name: string;
      url: string;
    };
    name: string;
  }

async function showPokeData(pokeId :string) {
    const res = await fetch(`${baseUri}${pokeId}`);
    const json = await res.json();
    const pokeData = json.sprites.front_default;
    return pokeData
}
searchButton.addEventListener('click', async () => {
    const pokeFrom:HTMLInputElement = document.querySelector('#poke-form')!;
        const searchKey:string = pokeFrom.value
        const imgData:string = await showPokeData(searchKey)
        await showPokeName(searchKey)
        imgFrom.src = imgData
}
)

randomButton.addEventListener('click', async ()=> {
    const randomId = getRandomId(POKEMON_MIN_ID, POKEMON_MAX_ID).toString();
    const imgData = await showPokeData(randomId)
    await showPokeName(randomId)
    imgFrom.src = imgData
})

function getRandomId(min :number,max :number): number {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}

async function showPokeName(pokeId: string) {
    const pokeName = document.querySelector('#poke-name')!;
    const res = await fetch(`${nameUri}${pokeId}`);
    const json = await res.json();
    const jaPokeName = json.names.find((nameEntry: PokemonLanguageEntry) => nameEntry.language.name === 'ja');
    pokeName.textContent = `${jaPokeName.name}`
}
```

```index.html
<!DOCTYPE html>
<html lang='ja'>
  <head>
    <meta charset="utf-8">
    <title>ポケモン検索</title>
  </head>
  <body>
    <h3>ポケモンを検索しよう！</h3>
    <p>図鑑Noを入れてね</p>
    <input type="text" id="poke-form">
    <button type="button" id="search-btn">検索</button>
    <button type="button" id="random-btn">ランダム検索</button>
    <div id="poke-display">
        <img id="pokemon-image" src="" alt="" width="200px">
        <p id="poke-name"></p> 
    </div>
    <script src="bundle.js"></script>
  </body>
</html>

```

### 機能説明
機能としては２つ
1.検索ボタンをクリックするとformに入れた値に合致するポケモンの画像データ、名前を表示する
2.ランダム検索をクリックすると1~1000の間でランダムにIDが割り当てられ、その値に合致するポケモンの画像データ、名前を表示する
今回はAPIでデータを取得する箇所に絞って解説していきます。

### 変数、定数の定義
```
const baseUri: string = "https://pokeapi.co/api/v2/pokemon/"
const nameUri: string = "https://pokeapi.co/api/v2/pokemon-species/"
const searchButton = document.querySelector('#search-btn')!;
const randomButton = document.querySelector('#random-btn')!;
const imgFrom: HTMLImageElement = document.querySelector('#pokemon-image')!;
const POKEMON_MAX_ID: number = 1000;
const POKEMON_MIN_ID: number = 1;

interface PokemonLanguageEntry {
    language: {
      name: string;
      url: string;
    };
    name: string;
  }
```
baseUriはエンドポイントのベースとなるURIを入れています。
これに対してformで入力された値、ランダムで生成された値を入れてrequestを送るようにします。
nameUriはポケモンの名前を取得するURIです、
各ポケモンの言語ごとの名前などの情報を返してくれるエンドポイントのURIを入れています。
imgFromでimgタグの要素を取得して、取得した画像データを入れられるようにします。
POKEMON_MAX_IDとかいうのはランダムで検索する際の上限値です。MINのほうが下限値です。
1000以上ありそうですがとりあえず仮で1~1000にしました。
PokemonLanguageEntryについては後ほど解説します。

### showPokeDataメソッド
```
async function showPokeData(pokeId :string) {
    const res = await fetch(`${baseUri}${pokeId}`);
    const json = await res.json();
    const pokeData = json.sprites.front_default
    return pokeData
}
```
pokeAPIのエンドポイントにリクエストを送るメソッドです。
引数でIDを受け取り、そのIDをbaseUriと結合してリクエストを送っています。
asyncは非同期関数の宣言です。
awaitをつけることでサーバーから結果が返ってくるのを待ってくれるようになります。
async awaitについてははこちら

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/async_function

javascriptでリクエストを送りたい時はfetchメソッドを使います。
引数にURIを入れて実行することで
HTTPレスポンス全体（ステータスコード、ヘッダー、本体等）を含む、responseオブジェクトを返してくれます。

https://developer.mozilla.org/ja/docs/Web/API/fetch

```
const pokeData = json.sprites.front_default
```
res.json()で、レスポンス本体をJSON形式に変換して、それをJavaScriptオブジェクトとして扱うことができるようにすることで上記のように直感的に値を取得することができます。

### 検索ボタンクリック時にfromの値をshowPokeDataに渡す
```
searchButton.addEventListener('click', async () => {
    const pokeFrom:HTMLInputElement = document.querySelector('#poke-form')!;
        const searchKey:string = pokeFrom.value
        const imgData:string = await showPokeData(searchKey)
        await showPokeName(searchKey)
        imgFrom.src = imgData
}
)
```
ここでの注意点は、HTMLInputElementと明示的に型を定義してあげないと
valueプロパティで入力値を取得しようとした時にelement型にvalueプロパティは存在しないとエラーが出ます。
valueプロパティはinput要素、select要素、textarea要素などの固有のプロパティなんですね。
初歩的なことですが、また一つ勉強になりました笑

そうして取得したイメージデータをimgFormのsrc属性に入れてあげることで表示できます。

### IDをランダムに生成する
```
function getRandomId(min :number,max :number): number {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}
```
　ランダム検索用にgetRandomIdメソッドを作成しました。
 Math.random()でランダムな値を生成して、Math.floorで整数に直しします。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Math/random

上記リンク先の2 つの値の間の乱数を得る
をそのまま引用しました。
無知な私は、このまま実行してたくさんの小数点と共に結果が出力されて困惑し以下のメソッドに辿り着きました。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Math/floor

> Math.floor() 関数は与えられた数値以下の最大の整数を返します。

ということなので、この結果に1を足せば１〜１０００の間でランダムに数字を生成してくれそうです。

### ランダム検索用のイベント
```
randomButton.addEventListener('click', async ()=> {
    const randomId = getRandomId(POKEMON_MIN_ID, POKEMON_MAX_ID).toString();
    const imgData = await showPokeData(randomId)
    await showPokeName(randomId)
    imgFrom.src = imgData
})
```
getRandomIdに対してtoStringを実行してるのは
エンドポイントであるURIは文字列型だからです。
showPokeDataの引数も文字列型なので、数値型のままだと入れられないしわけですね。
この辺りも実行前に教えてくれるのTypeScriptの型チェックは本当に素晴らしいですね。初学者に優しい。エラーを未然に防いで、特定にかかる無駄な時間を削減してくれます。

### showPokeNameメソッドで名前の取得
```
async function showPokeName(pokeId: string) {
    const pokeName = document.querySelector('#poke-name')!;
    const res = await fetch(`${nameUri}${pokeId}`);
    const json = await res.json();
    const jaPokeName = json.names.find((nameEntry: PokemonLanguageEntry) => nameEntry.language.name === 'ja');
    pokeName.textContent = `${jaPokeName.name}`
}
```

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/find

日本語のポケモンの名前の取得に意外と手間取ってしまいました。
エンドポイント下記の記事を参考に知りました。

参考記事

https://qiita.com/jinto/items/9d0d9df30dded1e61015

```
"names": [
        {
            "language": {
                "name": "ja-Hrkt",
                "url": "https://pokeapi.co/api/v2/language/1/"
            },
            "name": "フシギダネ"
        },
        {
            "language": {
                "name": "roomaji",
                "url": "https://pokeapi.co/api/v2/language/2/"
            },
            "name": "Fushigidane"
        },
        {
            "language": {
                "name": "ko",
                "url": "https://pokeapi.co/api/v2/language/3/"
            },
            "name": "이상해씨"
        },
        {
            "language": {
                "name": "zh-Hant",
                "url": "https://pokeapi.co/api/v2/language/4/"
            },
            "name": "妙蛙種子"
        },
        {
            "language": {
                "name": "fr",
                "url": "https://pokeapi.co/api/v2/language/5/"
            },
            "name": "Bulbizarre"
        },
        {
            "language": {
                "name": "de",
                "url": "https://pokeapi.co/api/v2/language/6/"
            },
            "name": "Bisasam"
        },
        {
            "language": {
                "name": "es",
                "url": "https://pokeapi.co/api/v2/language/7/"
            },
            "name": "Bulbasaur"
        },
        {
            "language": {
                "name": "it",
                "url": "https://pokeapi.co/api/v2/language/8/"
            },
            "name": "Bulbasaur"
        },
        {
            "language": {
                "name": "en",
                "url": "https://pokeapi.co/api/v2/language/9/"
            },
            "name": "Bulbasaur"
        },
        {
            "language": {
                "name": "ja",
                "url": "https://pokeapi.co/api/v2/language/11/"
            },
            "name": "フシギダネ"
        },
        {
            "language": {
                "name": "zh-Hans",
                "url": "https://pokeapi.co/api/v2/language/12/"
            },
            "name": "妙蛙种子"
        }
    ]
```
レスポンスの中のnamesという配列に言語ごとの情報があります。
このnames配列のLanguageがjaの配列のnameを取得して表示すればできそうです。
配列から特定の条件に合致するデータを取得したい場合は

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/find

findメソッド使用すれば良さそうです。
ただここで、型エラーが発生して詰まりました。
findメソッドに渡す引数の型エラーが起きてしまいまして。
じゃあこの引数の型は何になるのかというと
オブジェクト型になるようです。
```
       {
            "language": {
                "name": "ja",
                "url": "https://pokeapi.co/api/v2/language/11/"
            },
            "name": "フシギダネ"
        },
```
このようにnameとurlのプロパティを持つlanguageオブジェクトとnameプロパティという構造になっているため、それに合わせた構造の型を定義してあげないといけなかったわけですね。
新たなに型を定義するのには
interfaceというものを使用すればできるようです。
```
interface PokemonLanguageEntry {
    language: {
      name: string;
      url: string;
    };
    name: string;
  }
```
このように定義してあげることで、無事解決しました。




### まとめ
今回は、TypeScriptを使用しPokeAPIからデータを取得することに挑戦してみました。APIというと何となく難しさを感じ苦手意識がありましたが、実際に手を動かしデータを取得する過程を経験することで理解が深まったように思います！

自分なりに流れをまとめてみると

①データの特定: まず、欲しい情報がどのURIにあるのかを特定する。

②リクエストの送信: 次に、そのURIに対してリクエストを送り、データを「要求」します。

③データの抽出: リクエストに対するレスポンスを受け取った後、必要な情報を抽出する。
膨大な情報が帰ってくるので、その中で必要な情報を絞り込んでいく必要がありました。データ構造に合わせてアクセスしたり、特定の条件で絞り込んだりですね。

④画面上への反映: 最後に、取得したデータをHTML側に描画する。DOM操作で取得した要素にデータを入れてあげる。

という感じでしょうか。

エラー処理がなかったり、汚いコードだったり課題は盛りだくさんだと思いますが、その辺りも少しずつ学んでいけたらなと思います！