見出し画像

『JavaScriptを極める3ヶ月 アプリ開発に使える知識を徹底的に学ぶ』という勉強会の4日目をまとめる

X-HACK様の主催する勉強会の『JavaScriptを極める3ヶ月 アプリ開発に使える知識を徹底的に学ぶ』という勉強会の学んだことについて備忘録&復習としてまとめてみようと思います。


※この記事を見る前提知識としてJavaScriptの基本文法とオブジェクト指向&プロパティの概念、簡単なchromeデベロッパー・ツールの使い方を覚えておくと読みやすいと思います。

前回はこちら

こちらも見てくださるとうれしいです。

1、今回のゴールと勉強になったこと

・前回のぐるなびAPIをClass設計などの考えを盛り込み
 コードをリファクタリングする。
ということがゴールとなります。

完成品はこちら。

前回とはリストのUIが少し違いますが、基本は一緒です。

2.JavaScriptのClass構文の登場した背景

JavaScriptにおけるClassという概念ですが、ES2015(ES6)から導入された
ものです。なぜ登場したかの背景を簡単に教えていきます。

JavaScriptは関数で生成、代入、演算、引数・戻り値の受け渡し
といったその言語における基本的な操作を制限なしに使用できると
いった第一級オブジェクトの扱いができます。
なのでJavaScriptは第一級関数という性質をもちます。

ES2015までのJavaScriptでは関数を使って
今のClassのようなことを書いていたのですが、それだと
見た目がゴチャゴチャしてがちで見にくいということが
よくありました。

それでES2015からClass構文というものが登場し
コードがすっきりと書きやすくなり可読性が高まりました。
同時に他のオブジェクト指向言語を使ったことがある
人からも扱いやすくなりました。

ちなみに、JavaScriptはプロトタイプベースという
オブジェクト指向言語のスタイルです。

C言語には関数プロトタイプ宣言というものがあるが、
これは関数・サブルーチンの引数と返り値の値を宣言するものである。
外部とのやりとりを示す「宣言」に対し、
中身を示すものを「定義」と言う。
プロトタイプベースのオブジェクト指向プログラミングでは、
プロトタイプは「クローンとしての新しいオブジェクト」
を作ることができるオブジェクト、のことである。
逆にそのクローンの側から見ると、自分がクローンとして
作り出される元となったオブジェクトがプロトタイプである。
引用:プロトタイプ・フリー百科事典『ウィキペディア(Wikipedia)』

JavaScriptでは
関数=プロトタイプ
宣言された関数=クローンという認識でいいでしょう。

そこにクラスベースというオブジェクト指向のスタイルが入ってきたことで
JavaとかC++をやってきた人は学びやすくなったということですね。

※オブジェクト指向言語は、クラスベースとプロトタイプベースとあり
 どちらも異なるものとあります。が、僕はクラスベースの言語は
 まだ触ってないです。なので、「クラスベースの考えが入った
 プロトタイプベース
」という認識でいます。
 MDNでも

ECMAScript 2015 で導入された JavaScript クラスは、JavaScript にすでにあるプロトタイプベース継承の糖衣構文です。
クラス構文は、新しいオブジェクト指向継承モデルを JavaScript に導入しているわけではありません。

 というふうに書いていますしね。
 「JavaScriptはクラスベースとプロトタイプベース
 両方の性質を持ってる!」  
 って言ったら他のエンジニアの方から怒られそうですし笑。

 Class構文についてはこちらで

3.HTML・CSSを見てみる

<!doctype html>
<html lang="ja">

<head>
 <meta charset="utf-8" />
 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 <title>X-HACK ぐるなびAPI</title>
 <link rel="stylesheet" href="https://dhbhdrzi4tiry.cloudfront.net/cdn/sites/foundation.min.css">
 <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" />
 <link rel="stylesheet" href="./css/style.css">
</head>

<body>

 <!-- ヘッダー -->
 <div class="title-bar" data-responsive-toggle="realEstateMenu" data-hide-for="small">
   <button class="menu-icon" type="button" data-toggle></button>
   <div class="title-bar-title">Menu</div>
 </div>
 <div class="top-bar" id="realEstateMenu">
   <div class="top-bar-right">
     <ul class="menu">
       <li><a href="#">My Account</a></li>
       <li><a class="button">Login</a></li>
     </ul>
   </div>
 </div>
 <br>

 <!-- 検索フォーム -->
 <div class="row">
   <div class="small-2 large-2 columns">検索ワード</div>
   <div class="small-4 large-4 columns">
     <input id="search-id" class="" type="text" placeholder="検索ワードを入力してください" value="タピオカ"/>
   </div>
   <div class="small-6 large-4 columns">
     <a class="button" onclick="loadUrl()">検索</a>
     <a class="button" onclick="favoriteList()">お気に入りリスト</a>
   </div>
 </div>

 <!-- リスト -->
 <div id="main-block" class="row small-up-1 medium-up-2 large-up-3">
 </div>

 <!-- フッター -->
 <footer>
   <div class="row">
     <div class="medium-6 columns">
       <ul class="menu float-right">
         <li class="menu-text">X-HACK</li>
       </ul>
     </div>
   </div>
 </footer>

 <script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
 <script src="https://dhbhdrzi4tiry.cloudfront.net/cdn/sites/foundation.js"></script>
 <script src="./js/main.js">
 </script>
</body>

</html>

前回とは大きな違いがありません。
変わったことといえば<script src="./js/main.js">という風に
HTMLとCSS、JavaScriptを分けて書いたくらいでしょう。
CSSはこちらになります。

.sample-box {
 position: relative;
}

.sample-box img {
 width: 100%;
 height: 200px;
}

.favorite {
 width: 40px;
 height: 40px;
 color: #db197452;
 background-color: #f8c3d9dc;
 display: flex;
 justify-content: center;
 align-items: center;
 font-size: 1.5rem;
 border-radius: 50%;
 cursor: pointer;

 position: absolute;
 top: 8px;
 right: 8px;
}

.on {
 color: #f80376;
}

次にJavaScriptを見てみましょう。

4.JavaScriptを見てみる

$(document).foundation();

// 本来はサーバー側で処理してユーザーからは見えないようにする
const API_KEY = "省略";
const MAIN_BLOCK = document.getElementById("main-block");
//お気に入りリストの処理クラス
class FavoriteShops {

 constructor(){
   this.FAVORITE_SHOPS_KEY = "favorite_shops";
   this.favorite_shops = localStorage.getItem(this.FAVORITE_SHOPS_KEY);
   //!は否定演算子
   if (!this.favorite_shops) {
     this.favorite_shops = [];
   } else {
     this.favorite_shops = this.favorite_shops.split(",");
   }
 }

 // 引数に与えられたデータを配列に追加する処理
 add(id) {
   // 早期リターン early return
   if (this.favorite_shops.includes(id)) return;

   this.favorite_shops.push(id);
   localStorage.setItem(this.FAVORITE_SHOPS_KEY, this.favorite_shops);
 }

 // 引数に与えられたデータを配列から削除する処理
 remove(id){
   this.favorite_shops = this.favorite_shops.filter((item) => {
     if (item != id) return item;
   });
   localStorage.setItem(this.FAVORITE_SHOPS_KEY, this.favorite_shops);
 }
}
let favshops = new FavoriteShops();

// API 呼び出しの関数
function loadUrl() {
 // 全ての子要素を削除する
 while (MAIN_BLOCK.firstChild) MAIN_BLOCK.removeChild(MAIN_BLOCK.firstChild);
 // 検索ワードを取得する
 let searchData = document.getElementById("search-id").value;
 let url = `https://api.gnavi.co.jp/RestSearchAPI/v3/?keyid=${API_KEY}&freeword=${searchData}`;
 sendRequest(url);
}


//お気に入り機能を見せる
//ローカルストレージ(favorite_shops)内のIdを取得
//→取得したfavorite_shopsをfor繰り返しリクエストする
//→各お気に入り店のIdを入れたURLをfunction sendRequestで処理してMAIN_BLOCKで表示
function favoriteList(){
 while (MAIN_BLOCK.firstChild) MAIN_BLOCK.removeChild(MAIN_BLOCK.firstChild);
 for (let i = 0;i < favshops.favorite_shops.length; i++){
   let url = `https://api.gnavi.co.jp/RestSearchAPI/v3/?keyid=${API_KEY}&freeword=${favshops.favorite_shops[i]}`;
   sendRequest(url);
 }
}

function sendRequest(request_url) {
 // Ajax(XMLHttpRequest)処理
 // APIを実行して結果のJSONデータを加工している
let xhttp = new XMLHttpRequest();
// 通信が終わった時の処理
xhttp.onload = function () {
  let res = JSON.parse(xhttp.responseText);
  for (let i = 0; i < res.rest.length; i++) {
   let card = new CardItem(res.rest[i]);
   MAIN_BLOCK.appendChild(card.node);
  }
};
// データ取得開始
xhttp.open("GET", request_url, true);
xhttp.send();
}


// カードブロックのクラス
// レストランの1店舗の情報が集約されたクラス
class CardItem {
 constructor(item) {
   this.id = item.id;
   this.node = document.createElement("div");
   this.node.classList.add("column");
   this.node.innerHTML = this.card_item(
     item.name,
     item.pr.pr_short,
     item.image_url.shop_image1,
     item.address,
   );
   this.fav_icon = this.node.querySelector(".favorite");
   // お気に入りリストに存在するか?
   if (favshops.favorite_shops.includes(this.id)) {
     this.fav_icon.classList.add("on");
   }
   // クリックした時の処理
   this.fav_icon.onclick = function () {
     let id = this.getAttribute("data-id");
     if (this.classList.contains("on")) {
       this.classList.remove("on");
       favshops.remove(id);
     } else {
       this.classList.add("on");
       favshops.add(id);
     }
   }
   // 要素のカスタム属性(HTML5カスタムデータ属性)に識別子をセットする
   this.fav_icon.setAttribute("data-id", this.id);
 }

 card_item(title, text, image, address) {
   return `
     <div class="card" style="width: 300px;">
       <div class="card-divider">
         ${title}
       </div>
       <div class="sample-box">
         <img class="image" src="${image}">
         <div class="favorite">
           <i class="fa fa-star"></i>
         </div>
       </div>
       <div class="card-section">
         <p>${text}</p>
         <p>${address}</p>
       </div>
     </div>
   `;
 }
}

※おおまかなClass&関数と中身を見てみましょう。

・class FavoriteShops→お気に入りリストに関する処理をするクラス

・function loadUrl→検索ボタンを押したときに検索ワードを取得し
 そのURLを関数sendRequestへ送る

・function favoriteList→お気に入りボタンを押したとき
 リストに登録した店のIDを取得しそのURLを関数sendRequestへ送る

・function sendRequest→上2つから送られたURLを使い
 Ajax処理を行い、画像情報などをclass CardItemへ送りリスト表示させる。

・class CardItem→関数sendRequestから送られた情報をリスト表示させる
 ための処理をする。

という形ですね!
一つずつ見ていきましょう

・グローバルスコープのコード

$(document).foundation();

// 本来はサーバー側で処理してユーザーからは見えないようにする
const API_KEY = "省略";
const MAIN_BLOCK = document.getElementById("main-block");

ここでは全ての関数に使用できる定数を置いています。
ここでは
・APIキー
・HTMLでいうリスト部分のところをDOM操作するための定数
の2つを置いていますね。

・class FavoriteShops

class FavoriteShops {

 constructor(){
   this.FAVORITE_SHOPS_KEY = "favorite_shops";
   this.favorite_shops = localStorage.getItem(this.FAVORITE_SHOPS_KEY);
   //!は否定演算子
   if (!this.favorite_shops) {
     this.favorite_shops = [];
   } else {
     this.favorite_shops = this.favorite_shops.split(",");
   }
 }

 // 引数に与えられたデータを配列に追加する処理
 add(id) {
   // 早期リターン early return
   if (this.favorite_shops.includes(id)) return;

   this.favorite_shops.push(id);
   localStorage.setItem(this.FAVORITE_SHOPS_KEY, this.favorite_shops);
 }

 // 引数に与えられたデータを配列から削除する処理
 remove(id){
   this.favorite_shops = this.favorite_shops.filter((item) => {
     if (item != id) return item;
   });
   localStorage.setItem(this.FAVORITE_SHOPS_KEY, this.favorite_shops);
 }
}
let favshops = new FavoriteShops();

Class構文出てきましたね!
・constructor
・addメソッド
・removeメソッド
そして、変数favshopsを呼び出したらFavoriteShopsのインスタンスが
呼び出されるという形になります。
それでは、ひとつずつ見ていきましょう。

・constructor

 constructor(){
   this.FAVORITE_SHOPS_KEY = "favorite_shops";
   this.favorite_shops = localStorage.getItem(this.FAVORITE_SHOPS_KEY);
   //!は否定演算子
   if (!this.favorite_shops) {
     this.favorite_shops = [];
   } else {
     this.favorite_shops = this.favorite_shops.split(",");
   }
 }

⓵「this.FAVORITE_SHOPS_KEY」に「favorite_shops」を代入
⓶「this.favorite_shops」「localStorage.getItem(this.FAVORITE_SHOPS_KEY)」
 を代入させる(※これによりthis.favorite_shopsを呼び出したら
 ローカルストレージ内のお気に入り登録したIDを受け取れる)
⓷if分岐「this.favorite_shopsに文字はないか?」で分岐して
 真なら「this.favorite_shops」に[](New array)を与え
 偽ならば配列「this.favorite_shops」の「,」の部分で区切りをつける

・addメソッド

  add(id) {
   if (this.favorite_shops.includes(id)) return;

   this.favorite_shops.push(id);
   localStorage.setItem(this.FAVORITE_SHOPS_KEY, this.favorite_shops);
 }

⓵if分岐「this.favorite_shopsに(追加するID)が含まれているか」で分岐
 真なら、add(id)の実行を中止する(return)
 偽なら「this.favorite_shops」にIDを加え
  「this.favorite_shops」を「this.FAVORITE_SHOPS_KEY」という
 ローカルストレージキーに加える

早期リターン(early return)をすることでIf分のネストを防いでいます。

・removeメソッド

  remove(id){
   this.favorite_shops = this.favorite_shops.filter((item) => {
     if (item != id) return item;
   });
   localStorage.setItem(this.FAVORITE_SHOPS_KEY, this.favorite_shops);
 }

⓵「this.favorite_shops」にfilterをかけて、その結果を
 「this.favorite_shops」に取得(取り除くIDをここで取り除いている。)
②①の結果を「localStorage.setItem」する。

分かりにくいので動画でremove(id)をデベロッパー・ツール
を使って見てみます。前提条件として
this.favorite_shops = [ggmb700,ggk9200,ggk9600,k510302 ]
としまして、ggmb700(店名Lan Che)を取り除いてみます。

右下の「Whatch」タブの「item」に注目してみましょう。「this.favorite_shops」のID4つ「item」に渡してフィルターを
かけていていますね。
ggmb700以外の3つは「return item」で「this.favorite_shops」に
セットしていますがggmb700は「if (item != id)」では偽となるので
this.favorite_shopsに戻していません。
残った3つのIDを「this.favorite_shops」にもう一度
ローカルストレージにセットしています。
これがremove(id)の仕組みです。


以下が今回出てきたメソッド等です。配列メソッドが多いですね。



ここで大切なことを1つ。constructorの時点で「this.favorite_shops」に
文字があれば「split(",")」で「,」のところに区切りをつけています。

なぜ、このような形にしているのかといいますと
「this.favorite_shops」を区切らずにIDを加えているとそのデータは
文字列のままなのです。なので、「,」で区切って文字列から配列へ
変化させる必要があった
のです。

例示コード

let moji = "a1,b2,c3,d4";

moji
> "a1,b2,c3,d4"
moji[0]
> "a"
moji.length
> 11

moji = moji.split(",")
> (4) ["a1", "b2", "c3", "d4"]
moji[0]
> "a1"
moji.length
> 4

後述コードで書きますがにお気に入りリストを表示させるときにIDを1つずつ取り出す必要があったので、松田さんに相談したら
上記のことを教えていただきました。
「localStorage.getItem("favorite_shops")」のままではなく、
「this.favorite_shops = this.favorite_shops.split(",")」で取得しないと
IDは取得されない
ということです。なるほど!

ちなみにこれらのthisは「FavoriteShops」を呼び出しています。

後述しますがこれらのクラスの中身は変数favshopsを呼び出したとき新しいインスタンスを呼び出します。


・function loadUrl

function loadUrl() {
 while (MAIN_BLOCK.firstChild) MAIN_BLOCK.removeChild(MAIN_BLOCK.firstChild);
 // 検索ワードを取得する
 let searchData = document.getElementById("search-id").value;
 let url = `https://api.gnavi.co.jp/RestSearchAPI/v3/?keyid=${API_KEY}&freeword=${searchData}`;
 sendRequest(url);
}

①定数MAIN_BLOCKにあるリストを全て消去
②変数「searchData」にsearch-id(ここでいう検索ワード)
 に入力された文字を取得
③変数「url」にテンプレートリテラルで「API_KEY」と「searchData」を入れていき取得
④「url」を関数sendRequestに送る

前回の function loadUrl()と同じ働きを持っていますね。


・function favoriteList

function favoriteList(){
 while (MAIN_BLOCK.firstChild) MAIN_BLOCK.removeChild(MAIN_BLOCK.firstChild);
 for (let i = 0;i < favshops.favorite_shops.length; i++){
   let url = `https://api.gnavi.co.jp/RestSearchAPI/v3/?keyid=${API_KEY}&freeword=${favshops.favorite_shops[i]}`;
   sendRequest(url);
 }
}

①定数MAIN_BLOCKにあるリストを全て消去
②forループで配列「favshops.favorite_shops」のIDの数だけ繰り返す命令を
 出す
③変数「url」にテンプレートリテラルで「API_KEY」と
 配列「favshops.favorite_shops[i]」を入れていきURLを取得
④「url」を関数sendRequestに送る

ここで前述した通り
「localStorage.getItem("favorite_shops")」のままで呼ぶのではなく
配列化された「favshops.favorite_shops」を呼んでおくことに
注意してください。


・function sendRequest

function sendRequest(request_url) {
let xhttp = new XMLHttpRequest();
// 通信が終わった時の処理
xhttp.onload = function () {
  let res = JSON.parse(xhttp.responseText);
  for (let i = 0; i < res.rest.length; i++) {
   let card = new CardItem(res.rest[i]);
   MAIN_BLOCK.appendChild(card.node);
  }
};
// データ取得開始
xhttp.open("GET", request_url, true);
xhttp.send();
}

なお、function loadUrl、function favoriteListからURLを渡されますが
その動きは若干違っています。

・function loadUrlの場合
 ①変数「xhttp」で「XMLHttpRequest」をnewで新しく取得
 ②HTTP通信が完了(=onload)されたら受け取ったURLにある
  JSONデータを解析し、記述されている値やオブジェクトをJavaScripに
  構築する。
 ③クラス「CardItem」のインスタンスを作成し、MAIN_BLOCKに
  繰り返し 「card.node」に追加していく。
 ④xhttp.openメソッドでURLにリクエスト、
  xhttp.sendメソットでサーバへリクエストを送信

・function favoriteListの場合
 ①変数「xhttp」で「XMLHttpRequest」をnewで新しく取得
 ②HTTP通信が完了(=onload)されたら受け取ったURLにある
  JSONデータを解析し、記述されている値やオブジェクトをJavaScripに
  構築する。
 ③クラス「CardItem」のインスタンスを作成し、MAIN_BLOCKに
  「card.node」に追加していく。
 ④xhttp.openメソッドでURLにリクエスト、
  xhttp.sendメソットでサーバへリクエストを送信
 ⑤function favoriteListの「 for (let i = 0;...」へ戻る
 ⑥お気に入り店をすべて表示させるまで①~⑤ループ

という形になりますね!


・class CardItem

class CardItem {
 constructor(item) {
   this.id = item.id;
   this.node = document.createElement("div");
   this.node.classList.add("column");
   this.node.innerHTML = this.card_item(
     item.name,
     item.pr.pr_short,
     item.image_url.shop_image1,
     item.address,
   );
   this.fav_icon = this.node.querySelector(".favorite");
   // お気に入りリストに存在するか?
   if (favshops.favorite_shops.includes(this.id)) {
     this.fav_icon.classList.add("on");
   }
   // クリックした時の処理
   this.fav_icon.onclick = function () {
     let id = this.getAttribute("data-id");
     if (this.classList.contains("on")) {
       this.classList.remove("on");
       favshops.remove(id);
     } else {
       this.classList.add("on");
       favshops.add(id);
     }
   }
   // 要素のカスタム属性(HTML5カスタムデータ属性)に識別子をセットする
   this.fav_icon.setAttribute("data-id", this.id);
 }

 card_item(title, text, image, address) {
   return `
     <div class="card" style="width: 300px;">
       <div class="card-divider">
         ${title}
       </div>
       <div class="sample-box">
         <img class="image" src="${image}">
         <div class="favorite">
           <i class="fa fa-star"></i>
         </div>
       </div>
       <div class="card-section">
         <p>${text}</p>
         <p>${address}</p>
       </div>
     </div>
   `;
 }
}

こちらにもClass構文出てきましたね!
Class構文出てきましたね!
ここではconstructorが主となりますがその中では
・JSONデータを使い、店データのオブジェクトを作る。
 それをcard_itemでhtmlの形に整理して
 「MAIN_BLOCK.appendChild(card.node)」でリスト表示される。
・お気に入りリストに存在するか?
・お気に入り追加する星マークをクリックしたらどのような反応するか。

という3つですね!見ていきましょう!

  constructor(item) {
   this.id = item.id;
   this.node = document.createElement("div");
   this.node.classList.add("column");
   this.node.innerHTML = this.card_item(
     item.name,
     item.pr.pr_short,
     item.image_url.shop_image1,
     item.address,
   );

①「this.id」に「item.id」を取得
②this.nodeに<div>とそのclass「column」を追加
③this.card_itemのHTMLを追加
 ※これらのnoteは「MAIN_BLOCK.appendChild(card.node)」
  で表示される。

ここではHtmlとも絡んでくるので分かりにくいですが
下の画像を見れば分かりやすいかもしれません。

画像3

□で囲っているところが追加された要素となります。

ここでいう引数「item」というのは「res.rest[i]」のJSONデータです。
コンソールで確かめてみましょう

画像1

画像でわかるように「item」の中にJSONデータがとられてますね。
そして下の画像下線部のデータを取っていきます。

画像2

また、card_itemの引数をとり、このようなhtmlを追加していきます。

card_item(title, text, image, address) {
   return `
     <div class="card" style="width: 300px;">
       <div class="card-divider">
         ${title}
       </div>
       <div class="sample-box">
         <img class="image" src="${image}">
         <div class="favorite">
           <i class="fa fa-star"></i>
         </div>
       </div>
       <div class="card-section">
         <p>${text}</p>
         <p>${address}</p>
       </div>
     </div>
   `;
 }

「title, text, image, address」がテンプレートリテラルとなっており
柔軟に対応しやすく書かれてますね!
(松田さん曰くHTMLを変えたい部分だけテンプレートリテラルにすると
Webデザイナーとの衝突が起こりにくいらしいです。)

それらのデータを「this.node」に格納して
「MAIN_BLOCK.appendChild(card.node)」で表示させているのです。

次を見る前にこちらのコードを見てみましょう。

this.fav_icon.setAttribute("data-id", this.id);

これはリストが表示されるときに読み込まれるコードですが
ここでは「data-id」属性に「this.id」を渡すというメソッドです。
それを「this.fav_icon」へ渡しているのです。

それを踏まえたうえで次を見てみましょう。

this.fav_icon = this.node.querySelector(".favorite");
   if (favshops.favorite_shops.includes(this.id)) {
     this.fav_icon.classList.add("on");
   }

①「this.fav_icon = this.node.querySelector(".favorite");」で
 CSSの(.favorite)を「this.fav_icon」で取得
 (この地点でthis.fav_icon = div.favorite)
②if分岐「「favshops.favorite_shops」にIDが含まれているか?」で
 真ならCSSの(.on)を付ける(アイコンがピンクになる)

CSSを見てみましょう

.favorite {
 width: 40px;
 height: 40px;
 color: #db197452;
 background-color: #f8c3d9dc;
 display: flex;
 justify-content: center;
 align-items: center;
 font-size: 1.5rem;
 border-radius: 50%;
 cursor: pointer;

 position: absolute;
 top: 8px;
 right: 8px;
}

.on {
 color: #f80376;
}

「querySelector(.favorite)」でCSSを「this.fav_icon」で取得して星を作っていることが分かります。どこに(.on)がついているのか
分かりにくいので<div class="favorite">でクリックしたときを
みて変化を確かめましょう。

ここの動きはonclicの動きとなるのですがCSSにonが付け外れしているのが
分かったと思います。


次はthis.fav_iconにonclickされたときの動きを見てみます。

   this.fav_icon.onclick = function () {
     let id = this.getAttribute("data-id");
     if (this.classList.contains("on")) {
       this.classList.remove("on");
       favshops.remove(id);
     } else {
       this.classList.add("on");
       favshops.add(id);
     }
   }

①あらかじめセットされていた「data-id」を呼び出し
「 this.getAttribute("data-id")」で変数idで受け取る
②if分岐「「on」がついているか」で
 真ならばCSSの(.on)を取り除き、
 「favshops.remove(id)」で「class FavoriteShopsのremove(id)]」を実行する
 偽ならばCSSの(.on)を追加し
 「favshops.add(id)」で「class FavoriteShopsのadd(id)]」を実行する

ここでは上記の動きのコードとなりますね。
このようにdata-idとthis.idは紐づいているので、星をクリックしている
時と同時にローカルストレージ内のIDの追加、削除をしているのです。

上記の通り「this.classList」の「this」.はhtmlでいう
<div class="favorite">です。
thisのスコープ範囲を意識しないと分かりにくいですが
デベロッパー・ツールを活用して慣れていきましょう。

以上です。

5、まとめ

今回はいろいろ学べましたが特に

・関数が多いコードをClass構文を使いリファクタリングする

という考えが学べました。
実際、関数のみで書くのとはだいぶすっきりしましたね!
前回は関数が7つありましたが、今回はClassと関数で5まで減りました!

また、お気に入りの星をJSからHTMLへ表示させるとき

   function generateIcon(shop_id) {
    let icon_node = document.createElement("i");
    icon_node.shop_id = shop_id;
    icon_node.classList.add("fa");
    icon_node.classList.add("fa-star");
    icon_node.classList.add("fa-3x");
    icon_node.style.color = "black";

とNordカオスでしたが、今回は

this.node.innerHTML = this.card_item(
     item.name,
     item.pr.pr_short,
     item.image_url.shop_image1,
     item.address,
   );  


card_item(title, text, image, address) {
   return `
     <div class="card" style="width: 300px;">
       <div class="card-divider">
         ${title}
       </div>
       <div class="sample-box">
         <img class="image" src="${image}">
         <div class="favorite">
           <i class="fa fa-star"></i>
         </div>
       </div>
       <div class="card-section">
         <p>${text}</p>
         <p>${address}</p>
       </div>
     </div>
   `;
 }

と直接HTMLとして記入して追加しています。
どちらが見やすいかは一目瞭然でしょう!

他にも「Array.prototype」自分で配列を定義したり
ヘッドレスCMSであるStrapiを教えていただいたり
アルゴリズムのことも学びましたが今回は省略します。
機会があったらStrapiとかの使い方も書いていきたいですね!

「ここが違うよ」とか「ここ誤字じゃない?」というところがありましたらTwitterかnoteでコメントしてくださるとうれしいです!

次回は、いよいよVue.jsを触っていきます!
お楽しみに!

この記事が気に入ったらサポートをしてみませんか?