見出し画像

Vanilla JSでGitHubAPIから情報を取得する(Ajax通信をマスター)

こんにちは。

りゅーそうです。

タイトルの通り、今回はGitHubAPIを使ってリポジトリの情報を取得して、表示するWebページを作ってみました。

成果物はこちら。↓↓↓

こちらのページの作り方を紹介します。


対象読者

・JavaScriptの基本を理解している(ドットインストールなど)

・xhackのこちらの勉強会に参加された方

​・JavaScript Primerの読者​

・Ajax通信をfetchAPIで行ってみたい方

・async functionの処理を知りたい方

環境構築(構築と言うほどではない)

Nodeをインストールしてください。

node -v
v10.16.0
npm -v
6.9.0

おきまりのコマンド。

ーyを入力することによって、最初の煩わしい質問を吹っ飛ばす事ができます。

npm init -y

次はこれ。

こちらのinstallというコマンドも省略できます。

npm i   //npm installの省略形

今回はせっかくなので、便利ツールも試してみました。

Node.js上で、コードを整形してくれるPrettierを使ってみました。

eslintと組み合わせて使います。(eslintはGoogle準拠のもの)

以下のコマンドを入力。

npm i -D prettier-eslint prettier-eslint-cli eslint-config-google

ここまでで、入力したらpackage.jsonを以下のようにします。

{
 "scripts": {
   "format": "prettier-eslint --write 'src/**/*.js'"
 },
 "devDependencies": {
   "eslint-config-google": "^0.13.0",
   "prettier-eslint": "^9.0.0",
   "prettier-eslint-cli": "^5.0.0"
 }
}

これで、

npm run format

とコマンドに入力することによって、自動でコードを読みやすい形に整形してくれるのでとても便利です。(これもnpm scriptというタスクの自動化の一種です。)

ビルドはjsprimerさんが用意してくれていたものを使用したいと思います。

npx @js-primer/local-server

npxとはプロダクトにインストールしていないパッケージをローカルから実行してくれる便利コマンドです。プログラムを実行するときはこのコマンドを入力します。

ファイル構成

画像1

見にくくて、すいません。

package.json, package-lock.json,node_moduleはnpm initをした際に自動で作成されます。今回はCSSは書かないので、いじるのはindex.htmlとindex.jsのみになります。

HTMLを書く

特に特別なことはなし。オーソドックスなものを用意します。

後で、index.jsでいじっていくので要素にマークアップをしておきます。

<!DOCTYPE html>
<html lang="ja">
<head>
 <meta charset="UTF-8">
 <title>GitHubAPIApp</title>
 <link rel="stylesheet" href="css/style.css"/>
</head>
<body>
 <header> 
   <div class="header-container">
     <h1 class="header-logo">GitHub Repository Ranking</h1>
   </div>
 </header>
 <main>
   <div class="main-container">
     <p class="maincopy">GitHubの人気リポジトリを読んで、勉強しよう!
     <br>気になるプログラミング言語を入力して、検索してみてください。</p>
     <input id="userId" type="text" placeholder="検索ワードを入力してください"/>
     <button onclick="main();">検索</button>

     <div id="result"></div>
   </div>
 </main>
  <footer>
    <div class="footer-container">
      <p class="copyright">©Ryu-sou</p>
    </div>
 </footer>

 <script src="src/index.js"></script>
</body>
</html>

inputタグで入力フォームを作成。

buttonタグでボタンを作成。onclickイベントを与えて、ボタンをクリックしたらイベントが発生できるようにします。

ここまで。もちろん何も動きません。

画像2

async functionで非同期処理を行う

index.js

const result = document.getElementById('result');

async function main() {
 try {
   while (result.firstChild) result.removeChild(result.firstChild);
   const userId = getUserId();
   const userInfo = await fetchUserInfo(userId);
   for (let i =0; i < 10; i++) {
     createView(userInfo.items[i]);
   }
 } catch (error) {
   console.error(`eエラーが発生しました(${error})`);
 }
}

function getUserId() {
 
 //ここにinputタグに入力されたuseIdの値を取得し、返す処理を書く

}

function fetchUserInfo(userId) {

 //ここにGitHubAPIをfetch APIを使って呼び出す処理を書く

}


function createView(userInfo) {
 
 //ここに取得したGitHubのリポジトリ情報を元に、Webページに表示させる処理を書く

}

function escapeSpecialChars(str) {
 return str
     .replace(/&/g, '&amp;')
     .replace(/</g, '&lt;')
     .replace(/>/g, '&gt;')
     .replace(/"/g, '&quot;')
     .replace(/'/g, '&#039;');
}

function escapeHTML(strings, ...values) {
 return strings.reduce((result, str, i) => {
   const value = values[i - 1];
   if (typeof value === 'string') {
     return result + escapeSpecialChars(value) + str;
   } else {
     return result + String(value) + str;
   }
 });
}

では、処理をindex.jsに実装して行きます。

実際は処理を一つにまとめて書くところから、関数を分けてリファクタリングをしていくという手順で書いて行きましたが、流石にその手順を書くのは面倒なので、コードの解説に止めようと思います。

この辺りの開発手順の流れを体験されたいかたはjsPrimerをやられると良いと思います。

まずは1行目、resultというidを持った要素を取得します。これに、GitHubリポジトリの情報を埋め込んで行きます。

const result = document.getElementById('result');

次にmain()というイベントが発生された際に呼び出される関数を実装していこうと思います。

async function main() {
 try {
   //resultにすでに子要素が入っていた場合に、その子要素を取り除く処理
   while (result.firstChild) result.removeChild(result.firstChild);
   
   //フォームに入力された内容を取得してuserIdという関数に格納
   const userId = getUserId();
  
  //fetchAPIで取得したGitHubリポジトリの情報を取得し、userInfoに格納
   const userInfo = await fetchUserInfo(userId);
   
   //取得した情報をviewで表示し、10回反復処理を行う。
   for (let i =0; i < 10; i++) {
     createView(userInfo.items[i]);
   }

 } catch (error) {
   console.error(`eエラーが発生しました(${error})`);
 }
}

async functionについてはこちらの記事がわかりやすいので、参照。

この中で今回で重要なことを抜粋すると、

1. asyncはPromiseを返す。

2.await式を使うことで、処理が完了するまで非同期処理を止めてくれるので、同期処理のような書き方ができるので、コードが読みやすくなる。

3. awaitによって、例外処理をtry..catchの構文を使って書く事ができる。

という事です。

上のコードでは、tryの中でエラーが出ると、catchが呼び出されるという例外処理がされています。また、awaitをつかうことで、GitHubリポジトリの情報を取得する前に、後の処理が行われてしまうことを防いでいます。

このように、順番に非同期処理をかけるというのがasync functionのメリットなので、どんどん使って行きましょう。

また、今回のように処理ごとに関数を分ける手法はよく使われるので、参考にしてみてください。

フォームに入力された情報を取得する

function getUserId() {
 const value = document.getElementById('userId').value;
 return encodeURIComponent(value);
}

関数getUserId()を実装して行きます。

ここは単純ですが、userIdに入力された値を返すという処理がされています。ちなみにencodeURIComponentはこちら。

(めんどくさい説明はMDNに任せるスタイル)

fetchAPIでリポジトリ情報を取得する

function fetchUserInfo(userId) {
 return fetch(
     `https://api.github.com/search/repositories?q=${userId}+in:name&sort=stars&order=desc`
 )
     .then((response) => {
       if (!response.ok) {
         throw new Error(`${response.status}: ${response.statusText}`);
       } else {
         return response.json();
       }
     })
     .catch((error) => {
       throw new Error('ネットワークエラー');
     });
}

fetchUserInfo関数を実装して行きます。

ちなみにfetch APIとは

HTTP通信を行いリソースを取得するためのAPIです。 Fetch APIを使うことで、ページ全体を再読み込みすることなく指定したURLからデータを取得できます。 #jsprimer

GitHubAPIからデータを取得する方法は公式を参照しましょう。

例えばReactと検索したい場合はこうですね。

`https://api.github.com/search/repositories?q=React+in:name&sort=stars&order=desc`

React in nameでReactという名前を持っているリポジトリを取得

sort=starsと設定することによって、スター順に表示してくれます。

ぜひ、色々試してみると面白いと思います。

あとはPromise.thenで処理を続けて、catchでエラーハンドリングを行っています。

取得したリポジトリ情報をページに表示する

function createView(userInfo) {
 const node = document.createElement('div');
 node.setAttribute('class', 'column');
 const txt = escapeHTML`
 <h4>${userInfo.full_name}</h4>
 <img src="${userInfo.owner.avatar_url}" 
 alt="${userInfo.owner.login}" height="100">
 <dl>
   <dt>☆${userInfo.stargazers_count}</dt>
   <dt>URL <a href="${userInfo.html_url}">
   ${userInfo.html_url}</a></dt>
   <dt>${userInfo.description}</dt>
 </dl>
 `;
 node.innerHTML = txt;
 result.appendChild(node);
}

function escapeSpecialChars(str) {
 return str
     .replace(/&/g, '&amp;')
     .replace(/</g, '&lt;')
     .replace(/>/g, '&gt;')
     .replace(/"/g, '&quot;')
     .replace(/'/g, '&#039;');
}


function escapeHTML(strings, ...values) {
 return strings.reduce((result, str, i) => {
   const value = values[i - 1];
   if (typeof value === 'string') {
     return result + escapeSpecialChars(value) + str;
   } else {
     return result + String(value) + str;
   }
 });
}

最後です。まずは関数escapeSpecialCharsとescapeHTMLの解説から行きます。

HTMLをエスケープする処理が書かれています。これを書かないと<といった表記が変に読み込まれたりしてしまうそうです。ただこのように、記述することはなく、エスケープしてくれるライブラリなどを使うのが一般的だそうです。

これによって、

const txt = escapeHTML`
 <h4>${userInfo.full_name}</h4>
 <img src="${userInfo.owner.avatar_url}" 
 alt="${userInfo.owner.login}" height="100">
 <dl>
   <dt>☆${userInfo.stargazers_count}</dt>
   <dt>URL <a href="${userInfo.html_url}">
   ${userInfo.html_url}</a></dt>
   <dt>${userInfo.description}</dt>
 </dl>
 `;

こういったHTMLを埋め込む実装が可能になるという話でした。

取得したいデータは先ほどのGitHubAPIのページで探して色々表示させて見てください。

あとは基本的なDOM操作になるので、DOM操作について理解したい方はぜひこちらのレポートも合わせてご覧ください。

完成!!

全コードはこちら

const result = document.getElementById('result');

// eslint-disable-next-line require-jsdoc
async function main() {
 try {
   while (result.firstChild) result.removeChild(result.firstChild);
   const userId = getUserId();
   const userInfo = await fetchUserInfo(userId);
   for (let i =0; i < 10; i++) {
     createView(userInfo.items[i]);
   }
 } catch (error) {
   console.error(`eエラーが発生しました(${error})`);
 }
}

// eslint-disable-next-line require-jsdoc
function fetchUserInfo(userId) {
 return fetch(
     `https://api.github.com/search/repositories?q=${userId}+in:name&sort=stars&order=desc`
 )
     .then((response) => {
       if (!response.ok) {
         throw new Error(`${response.status}: ${response.statusText}`);
       } else {
         return response.json();
       }
     })
     .catch((error) => {
       throw new Error('ネットワークエラー');
     });
}

// eslint-disable-next-line require-jsdoc
function getUserId() {
 const value = document.getElementById('userId').value;
 return encodeURIComponent(value);
}

// eslint-disable-next-line require-jsdoc
function createView(userInfo) {
 const node = document.createElement('div');
 node.setAttribute('class', 'column');
 const txt = escapeHTML`
 <h4>${userInfo.full_name}</h4>
 <img src="${userInfo.owner.avatar_url}" 
 alt="${userInfo.owner.login}" height="100">
 <dl>
   <dt>☆${userInfo.stargazers_count}</dt>
   <dt>URL <a href="${userInfo.html_url}">
   ${userInfo.html_url}</a></dt>
   <dt>${userInfo.description}</dt>
 </dl>
 `;
 node.innerHTML = txt;
 result.appendChild(node);
}

// eslint-disable-next-line require-jsdoc
function escapeSpecialChars(str) {
 return str
     .replace(/&/g, '&amp;')
     .replace(/</g, '&lt;')
     .replace(/>/g, '&gt;')
     .replace(/"/g, '&quot;')
     .replace(/'/g, '&#039;');
}

// eslint-disable-next-line require-jsdoc
function escapeHTML(strings, ...values) {
 return strings.reduce((result, str, i) => {
   const value = values[i - 1];
   if (typeof value === 'string') {
     return result + escapeSpecialChars(value) + str;
   } else {
     return result + String(value) + str;
   }
 });
}

フォームに言語などを入力すると検索結果が10件表示されます。

画像3

まとめ

今回はxhackさんの勉強会で学んだことと、jsPrimerを読んで得た知識を中心にコードを書いて見ました。

インプットした後に、それを元にアレンジして色々作ってみるというのはオススメの勉強だと思います。

ぜひ本記事を参考に色々なデータを返して見てください。

これからやりたいこと

FIrebaseでデプロイしようかなあ。

CSS Grid Layoutを使ってレイアウトをする。

参考記事:

ありがとうございました。

もしコード等、こういった書き方の方が良い!という意見ありましたら、くださるとありがたいです!

あ、もし参考になったらサポートください笑

では!

りゅーそうでした!!

サポートくださると励みになるので、よろしくお願い致します!サポートは全てプログラミングの勉強に使わせていただきます。またアウトプットして還元します^_^