Fetch API + Vue.js でGoogle Books APIから取得した書籍情報を表示する

Vue.js と Fetch API を組み合わせた利用方法の個人的なメモです。

Vue.js のサンプルコードを見ていると、Ajax通信に [axios](https://github.com/axios/axios) を使う例が多いようです。

一方で、素のJavaScript を利用してAjax通信を行う場合は、[XMLHttpRequest](https://developer.mozilla.org/ja/docs/Web/API/XMLHttpRequest)か、[Fetch API](https://developer.mozilla.org/ja/docs/Web/API/Fetch_API/Using_Fetch) を使うことになるかと思います。

[MDNのドキュメント](https://developer.mozilla.org/ja/docs/Web/API/Fetch_API/Using_Fetch)を見ていると、Fetch APIを利用できるブラウザが増えてきているようです。


そこで、今回はFetch APIでJSONデータを取得して、Vue.js でレンダリングを行ってみました。


## やること

* Google Books APIから、Fetch API を利用して書籍情報を取得
* 取得したJSONをVue.js で読み込み、「書籍名」「著者名」「書籍の説明」を表示する
* 著者名で書籍の一覧データを取得して、ループ処理を行う


## Fetch APIについて

詳細は [こちらの記事](https://developer.mozilla.org/ja/docs/Web/API/Fetch_API) を御覧ください。

## 基本的な書式

GETの場合はこんな感じ。jQueryのAjaxの書式に近い感じです。



```javascript

   fetch('APIのURL')
   
      .then(function (response) {

     // Do Something

      }).catch(function (error) {
        console.log(error);
      });
```

もしくは

```javascript

   fetch('APIのURL', {
  
     method: 'GET',
     headers: new Headers({
      'user-agent': 'Mozilla/5.0 (Android; Mobile; rv:21.0) Gecko/21.0 Firefox/21.0',
      'content-type': 'application/json',
     })

   }).then(function (response) {

    // Do Something
        
      }).catch(function (error) {
        console.log(error);
   });
```

などなど。


## Fetch APIでbodyを取得する方法

Fetch APIでREST APIと通信を行うと、ResponseをPromise オブジェクトとして返します。
API経由でJSONを取得した場合、JSONデータをjson()メソッドで一度返して上げる必要があります。

(最初これが良くわからず、しばらく悩みました)

APIから取得したJSONをconsole.logで表示する場合、Promise チェーンをつなげて、ResponseのBodyを返してから処理を行います。

```javascript

   fetch('APIのURL')
   
      .then(response => {

        return response.json();

      ).then(res => {

      console.log(res);

      }).catch(function (error) {

        console.log(error);

      });
```

## Google Books API からJSONデータを取得して、Vue.js で表示する

上記を踏まえて、Google Books API から書籍情報を取得して、Vue.js でレンダリングしてみました。

書籍は、古川日出男さんの読売文学賞受賞作、[「女たち三百人の裏切りの書」](https://www.googleapis.com/books/v1/volumes?q=isbn:9784103060765)で取得してみます。


```html
<!DOCTYPE html>
<html lang="ja">
<head>
  <title>Vue.js で Google Books API から書籍情報を表示するサンプルコード</title>
  <meta charset="utf-8">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.10/vue.js">
  </script>
</head>
<body>
  <ul id="example-1">
    <li>書籍名：{{ items[0].volumeInfo.title }}</li>
    <li>著者：{{ items[0].volumeInfo.authors[0] }}</li>
    <li>概要：{{ items[0].volumeInfo.description }}</li>
  </ul>
  <script>

  "use strict";

   fetch('https://www.googleapis.com/books/v1/volumes?q=isbn:9784103060765')

      .then(response =>  {

        return response.json();

      }).then( res => {

        const example1 = new Vue({
          el: '#example-1',
          data: res

        });
        
        
      }).catch(function (error) {
        console.log(error);
      });

  </script>
</body>
</html>
```

出力結果

![vue_1.png](https://qiita-image-store.s3.amazonaws.com/0/83767/498f41c3-6059-3bb8-8408-f8915fdd2ec9.png)

### Vueのインスタンスを生成後、Fetch を実行する

前項のサンプルコードでは、Fetch で書籍情報のJSONデータを取得後、Vue.js にJSONを渡してインスタンスを生成しました。

最初に Vue.js のインスタンスを生成後、dataオブジェクト に対してFetch から取得したJSONを格納する方法も考えてみました。Vue.js はdataオブジェクトの変更を検知して、[データに変化があった場合レンダリングを行います](https://jp.vuejs.org/v2/guide/instance.html) 。あらかじめ空のdataオブジェクト 「title」「author」「desc」を作っておき、Fetch したデータから書籍タイトル、著者名、概要を格納します。

下記のコードの出力結果は、上記と同じです。

```html
<!DOCTYPE html>
<html lang="ja">
<head>
  <title>Vue.js で Google Books API から書籍情報を表示するサンプルコード</title>
  <meta charset="utf-8">
  <script src="https://unpkg.com/vue/dist/vue.js"></script>
</head>
<body>
  <ul id="example-1">
    <li>書籍名：{{ title }}</li>
    <li>著者：{{ author }}</li>
    <li>概要：{{ desc }}</li>
  </ul>
  <script>

  "use strict";

   const vm = new Vue({
     el: '#example-1',
     data: {
       title  : '',
       author : '',
       desc   : '',
     },
   });

const json = fetch('https://www.googleapis.com/books/v1/volumes?q=isbn:9784103060765');


Promise.resolve(json).then (result => {

  return result.json();

}).then(response =>  {

     vm.title  = response.items[0].volumeInfo.title;
     vm.author = response.items[0].volumeInfo.authors[0];
     vm.desc   = response.items[0].volumeInfo.description;

   }).catch(function (error) {
        console.log(error);
   });

  </script>
</body>
</html>
```





## v-forを使って、書籍情報一覧を出力

次は、Google Books API から著者名で検索を行い、書籍情報の一覧をループで表示して、詳細ページへのリンクを貼ってみます。
Google Books APIのエンドポイントは以下とします。

```
https://www.googleapis.com/books/v1/volumes?q=%E5%8F%A4%E5%B7%9D%E6%97%A5%E5%87%BA%E7%94%B7
```

コードはこんなふうに書いてみました。

```html
<!DOCTYPE html>
<html lang="ja">
<head>
  <title>Vue.js で Google Books API から書籍情報を表示するサンプルコードその2</title>
  <meta charset="utf-8">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.10/vue.js">
  </script>
</head>
<body>

  <h1>古川日出男の書籍一覧</h1>
  <ul id="example-1">
    <li v-for="item in items">
      <a v-bind:href="item.volumeInfo.canonicalVolumeLink">
      
         {{ item.volumeInfo.title }} 
      
      </a>
    </li>
  </ul>
  <script>

  "use strict";

   fetch('https://www.googleapis.com/books/v1/volumes?q=%E5%8F%A4%E5%B7%9D%E6%97%A5%E5%87%BA%E7%94%B7')

      .then(response =>  {

        return response.json();

      }).then( res => {

        const example1 = new Vue({
          el: '#example-1',
          data: res

        });
        
        
      }).catch(function (error) {
        console.log(error);
      });

  </script>
</body>
</html>

```

Vue.js では、URLへのリンクは [以下のように記述してバインドする](https://jp.vuejs.org/v2/guide/syntax.html#%E5%BC%95%E6%95%B0) とのことです。

```html
<a v-bind:href="url"> ... </a>
```

出力結果。意図通りに出力できました。

![vue_2.png](https://qiita-image-store.s3.amazonaws.com/0/83767/cb986c2d-02c9-b8a1-9305-f5e653a87fd8.png)


### Vueのインスタンスを生成後、Fetch を実行する

先程と同様に、最初に Vue.js のインスタンスを生成後、dataオブジェクト に対してFetch から取得したJSONを格納する方法も試してみました。

下記のコードの出力結果は、上記のリスト表示と同じです。

```html
<!DOCTYPE html>
<html lang="ja">
<head>
  <title>Vue.js で Google Books API から書籍情報を表示するサンプルコードその2</title>
  <meta charset="utf-8">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.10/vue.js">
  </script>
</head>
<body>
  <h1>古川日出男の書籍一覧</h1>
  <ul id="example-1">
    <li v-for = "item in res.items">
      <a v-bind:href="item.volumeInfo.canonicalVolumeLink">

        {{ item.volumeInfo.title }} 

      </a>
    </li>
  </ul>
  <script>

  "use strict";

   const vm = new Vue({
     el: '#example-1',
     data: {
       res: '',
     },
   });

  const json = fetch('https://www.googleapis.com/books/v1/volumes?q=%E5%8F%A4%E5%B7%9D%E6%97%A5%E5%87%BA%E7%94%B7');


  Promise.resolve(json).then (result => {

  return result.json();

  }).then(response =>  {

   vm.res = response;
   console.log(vm.res.items);
   
   }).catch(function (error) {
        console.log(error);
   });

  </script>
</body>
</html>
```


## 複数の非同期通信を実行してからレンダリングする


Vue.js を触っていて、

* REST APIでデータを取得
* APIからの戻り値から値を取得して、別なAPIへ問い合わせ
* 2つ目のAPIからの戻り値を取得
* 2つのAPIの戻り値から、Vue.js でレンダリング

のように、複数の非同期通信を行った後にレンダリングをしたいと考えました。

上記のサンプルシナリオとして、以下のようなケースを考えてみます。

* Google Books APIから、書籍情報を取得
* 書籍情報から、ISBNの値を取得
* ISBNから、書籍情報を再取得
 * (=最初と同じJSONデータを取得) 
* 画像のサムネイルURLを取得
* 書籍の情報、および画像のサムネイルを合わせて表示

(同じ書籍データを2度問い合わせる、という無駄な処理ですが、テストケースとして作ってみました)


以下、コードです。

```html

<!DOCTYPE html>
<html lang="ja">
<head>
  <title>Vue.js で Google Books API から書籍情報を表示するサンプルコード</title>
  <meta charset="utf-8">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.10/vue.js">
  </script>
</head>
<body>
  <div id="example-1">
    <ul>
      <li>書籍名：{{ json.items[0].volumeInfo.title }}</li>
      <li>著者：{{ json.items[0].volumeInfo.authors[0] }}</li>
      <li>概要：{{ json.items[0].volumeInfo.description }}</li>
    </ul>
    <p>画像：<img :src=imgsrc ></p>
  </div>

  <script>

  "use strict";

   fetch('https://www.googleapis.com/books/v1/volumes?q=isbn:9784103060765')

      .then(response =>  {

        return response.json();

      }).then( res => {
      
        const example1 = new Vue({
         el: '#example-1',
         data: {
           json: res,
           imgsrc: [],
        },
        created: function() {
          this.getData();
        },
        methods: {
        
          getData: function(){

           fetch('https://www.googleapis.com/books/v1/volumes?q=isbn:' + res.items[0].volumeInfo.industryIdentifiers[1].identifier).

              then(response =>  {
                  return response.json();
                }).then( res => {

                  const src = res.items[0].volumeInfo.imageLinks.smallThumbnail;
                  this.imgsrc = src;
                });
          },
        },
       
      });
    }).catch(function (error) {
        console.log(error);
      });

  </script>
</body>
</html>

```

ここでは、最初の書籍情報をdataの「json」に格納。

jsonからISBNの値を取得して、[created](https://jp.vuejs.org/v2/guide/instance.html#%E3%83%A9%E3%82%A4%E3%83%95%E3%82%B5%E3%82%A4%E3%82%AF%E3%83%AB%E3%83%80%E3%82%A4%E3%82%A2%E3%82%B0%E3%83%A9%E3%83%A0) と methods を利用して、再度 Fetchをかけました。
再Fetchは、関数「getData」を定義して実行しています。


実行結果は以下。期待通りに取得できました。

![vue_img.png](https://qiita-image-store.s3.amazonaws.com/0/83767/c094bf84-b95e-75d6-3e11-9ad0c588f0c2.png)


ServiceWorkerでは、サーバーとの通信に Fetch API を利用することもあり、今後Fetch APIを利用したコードも増えてくることでしょうね。

※複数の非同期通信処理について、「こんな書き方もあるよ！」という例がありましたらぜひ教えてください！


## 参考情報

[Fetch 概説 - Web API インターフェイス | MDN](https://developer.mozilla.org/ja/docs/Web/API/Fetch_API/Using_Fetch)
[Fetch API response.json()のレスポンスがPromise objectを返す](http://blog.yayoc.com/javascript/2016/12/09/es6-fetch-promise.html)
[Response - Web API インターフェイス | MDN](https://developer.mozilla.org/ja/docs/Web/API/Response)https://jp.vuejs.org/v2/guide/