【Nuxt.js】郵便番号検索APIを使ってデータ取得を学ぶ

ちゃんと理解できていなかった、「APIを使ってデータ取得する」ことについてまとめました。
APIからデータを取ってきて何かアプリを作ってみたいという方や、
Nuxt.jsでのデータ取得についても書いているので、Nuxt.jsを勉強している方にも読んでもらえると嬉しいです。

# 今回作成したもの
<img width="294" alt="スクリーンショット 2019-04-11 19.25.31.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/388011/851d30c0-aae4-5c9d-c3ce-6554f1abf5bd.png">


住所入力フォームでよくある、
郵便番号を入力してボタンを押したら自動で住所を入力してくれるプログラムです。
郵便番号検索APIは登録不要ですぐに使えます。


## 前提
・Node.jsはインストール済（バージョンは現時点でv10.13.0）
・Vue.js/Nuxt.jsはある程度理解している
（私は<a href="https://www.amazon.co.jp/Vue-js-Nuxt-js%E8%B6%85%E5%85%A5%E9%96%80-%E6%8E%8C%E7%94%B0%E6%B4%A5%E8%80%B6%E4%B9%83/dp/4798056596">Vue.js & Nuxt.js超入門</a>で勉強していました。また、<a href="https://qiita.com/ayapon/items/d93807e7699434279531">こちらの記事</a>も参考にしていただければと思います。）

#作成の手順
1. Nuxt.jsのプロジェクトを作成する
2. templateを作成する
3. 特定の郵便番号の住所データを表示してみる
4. 入力された郵便番号の住所データを表示する

- おまけ：エラーメッセージを表示する①
- おまけ：エラーメッセージを表示する②
- おまけ：入力欄をクリックすると初期の画面に戻す
- おわりに


## (1)Nuxt.jsのプロジェクトを作成する
プロジェクトを作成したいディレクトリに移動し、以下のコマンドを実行します。
私はDesktopのpracticeというフォルダに`sample-search-zipcode-app`という名前のプロジェクトを作成しました。

```ターミナル
$ cd Desktop/practice
$ npx create-nuxt-app sample-search-zipcode-app
```

ProjectNameなど確認する表示が出てきますが、
`Choose features to install`以外は初期設定のままEnterを押していって大丈夫です。
`Choose features to install`についてはAxiosを選択してEnterを押してください。

installが完了したら、以下のように作成したプロジェクトに移動して実行します。

```ターミナル
$ cd sample-search-zipcode-app
$ npm run dev
```

localhost:3000にアクセスして、以下の画面になっていればプロジェクトの作成は完了です。
<img width="1341" alt="スクリーンショット 2019-04-11 15.42.52.png" src="https://qiita-image-store.s3.amazonaws.com/0/388011/97b9ec0a-68fc-f6f6-81e7-a4989a655039.png">



## (2)templateを作成する
作成したプロジェクトのpagesフォルダのindex.vueファイルを開いて、template部分を作成します。

```pages/index.vue
<template>
  <section class="container">
    <input type="text" placeholder="郵便番号を入力"/>
    <button>住所自動入力</button>
      <p>都道府県：</p>
      <p>住所1：</p>
      <p>住所2：</p>
  </section>
</template>

<script>
</script>
```

最終的には、郵便番号を入力して住所自動入力ボタンを押したら
`郵便番号` `都道府県` `住所1` `住所2` が自動で表示されるようにしていきます。



## (3)特定の郵便番号の住所データを表示してみる
まずは、入力された郵便番号ではなく、
こちらで指定した郵便番号（今回は〒154-0024）の住所データを表示してみます。

```pages/index.vue
<template>
  <section class="container">
    <input type="text" placeholder="郵便番号を入力"/>
    <button>住所自動入力</button>
      <p>{{resultData}}</p>
      <p>都道府県：</p>
      <p>住所1：</p>
      <p>住所2：</p>
  </section>
</template>

<script>
const axios = require('axios');

let url = 'http://zipcloud.ibsnet.co.jp/api/search?zipcode=';

export default {
  async asyncData() {
    let zipcode = '154-0024';
    let result = await axios.get(url + zipcode);
    return { resultData: result.data.results[0] };
  }
}
</script>
```

こんな画面になっていたらOKです。
![スクリーンショット](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/388011/21a81edd-cd9f-dc47-7b32-b26887edf9e1.png)


もし`Module not found`のエラーが出たら、ターミナルで`$npm install axios`コマンドを実行して、
再度`$npm run dev`してみてください。

### -クロスドメイン問題
こんなエラーが出てしまったら、
![スクリーンショット 2019-04-11 18.20.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/388011/e191f380-bbfe-755a-0c9e-303d34081fe7.png)
一旦Chromeを終了して、
`$open /Applications/Google\ Chrome.app/ --args --disable-web-security --user-data-dir`コマンドでChromeを開いてください。

このエラーは、ほかのwebサイトからデータを取ってこようとすると制限がかかる
クロスドメイン問題が起こった場合に出ます。

### -axiosとは？
axiosは、webサーバーにアクセスして必要な情報をやりとりするための機能です。
（axiosはNuxt.jsのための機能ではなく、Javascriptの一般的なライブラリです。）
`const axios = require('axios');`と書くと、axiosを利用できるようになります。

### -asyncDataとは？
<a href="https://ja.nuxtjs.org/api/">asyncData</a>は非同期処理をするためのNuxt.jsのメソッドです。

非同期処理とは、実行できるようになってから実行する処理のことです。今回の場合だと、
`http://zipcloud.ibsnet.co.jp/api/search?zipcode=154-0024`にアクセスして
住所データを取得してから、そのあとの処理を行うようにしています。
関数の前に`await`をつけることで、その関数の結果が返ってくるまで待機します。
`await`を使う場合は非同期関数を宣言するために`async`を書いておく必要があります。

### −郵便番号検索API
<a href="http://zipcloud.ibsnet.co.jp/doc/api">郵便番号検索API</a>は、日本郵便が提供しているデータを株式会社アイビスが加工して提供しているAPIです。
例えば、〒154-0024の住所データを取得したい場合は、
`http://zipcloud.ibsnet.co.jp/api/search?zipcode=154-0024`にアクセスすると
このようにデータを見ることができます。
<img width="319" alt="スクリーンショット 2019-04-11 16.24.27.png" src="https://qiita-image-store.s3.amazonaws.com/0/388011/cd677f09-4106-6cce-509e-d49870f539f7.png">

この結果を変数`result`に入れておき、`resultData`というプロパティ名で、
今回取得したい住所データ部分`result.data.results[0]`をオブジェクトとして返します。

template内では、{{`プロパティ名`}}でデータを表示することができます。

## (4)入力された郵便番号の住所データを表示する
(3)ではasyncData内でデータ取得しましたが、asyncDataはコンポーネント生成時に自動的に実行されるため、
コンポーネントが表示された際にはすでにデータが表示されています。
これをボタンが押された時に、入力された郵便番号の住所データを表示するように、
data/methodsプロパティを使ってコードを書き換えていきます。

```pages/index.vue
<template>
  <section class="container">
    <input type="text" placeholder="郵便番号を入力" v-model="zipcode"/>
    <button @click="searchAddressInfo">住所自動入力</button>
      <p>都道府県：{{addressData['address1']}}</p>
      <p>住所1：{{addressData['address2']}}</p>
      <p>住所2：{{addressData['address3']}}</p>
  </section>
</template>

<script>
const axios = require('axios');

let url = 'http://zipcloud.ibsnet.co.jp/api/search?zipcode=';

export default {
  data() {
    return {
      zipcode: '',
      addressData: {}
    }
  },
  methods: {
    searchAddressInfo() {
      axios.get(url + this.zipcode).then((res) => {
        this.addressData = res.data.results[0];
      })
    }
  }
}
</script>
```

`154-0024`と入力→住所自動入力ボタンを押して、このような画面になっていたらOKです。
<img width="324" alt="スクリーンショット 2019-04-11 18.32.21.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/388011/cff0f069-cc9f-0d16-5656-c655871b3747.png">

<h3>-実装の流れ</h3>
dataプロパティに`zipcode`を用意して、入力された郵便番号とバインドします。
また、住所自動入力ボタンをクリックした時に`searchAddressInfo`を呼び出します。
そしてdataプロパティに`addressData`を用意して、
`searchAddressInfo`の中でAPIから取ってきた住所データを入れます。
最後に、`addressData`の`address1` `address2` `address3` プロパティの値を
それぞれtemplate内で表示します。

<h3>-get.then</h3>
`searchAddressInfo`の中では、get.thenを使ってデータを取得しています。
get.thenは以下の形で使うことができ、`res`の中に取得したデータが入っています。

```
axios.get( URL ).then((res) => { アクセス後の処理 });
```

## おまけ：エラーメッセージを表示する①
正しい郵便番号が入力されなくてデータが取得できなかった場合に、
`no data`とメッセージを表示するようにします。

```pages/index.vue
<template>
  <section class="container">
    <p>{{message}}</p>
    <input type="text" placeholder="郵便番号を入力" v-model="zipcode"/>
    <button @click="searchAddressInfo">住所自動入力</button>
      <p>都道府県：{{addressData['address1']}}</p>
      <p>住所1：{{addressData['address2']}}</p>
      <p>住所2：{{addressData['address3']}}</p>
  </section>
</template>

<script>
const axios = require('axios');

let url = 'http://zipcloud.ibsnet.co.jp/api/search?zipcode=';

export default {
  data() {
    return {
      zipcode: '',
      addressData: {},
      message: ''
    }
  },
  methods: {
    searchAddressInfo() {
      axios.get(url + this.zipcode).then((res) => {
        if(res.data.results == null) {
          this.message = 'no data'
          return;
        }
        this.addressData = res.data.results[0];
      })
    }
  }
}
</script>
```

dataプロパティに`message`を用意して、
もしデータが取得できていなかったら（取得したデータがnullだったら）`message`を`no data`とします。
例えば、`1`とか`a`とか間違った郵便番号を入れて`no data`と表示されていたらOKです。


## おまけ：エラーメッセージを表示する②
URLが間違っていたり、サーバーが停止してしまうなど、アクセスできなかった場合に
`error`とメッセージを表示するようにします。
get.thenのあとに、さらにcatchメソッドを追加してエラー処理をします。

```pages/index.vue
<template>
  <section class="container">
    <p>{{message}}</p>
    <input type="text" placeholder="郵便番号を入力" v-model="zipcode"/>
    <button @click="searchAddressInfo">住所自動入力</button>
      <p>都道府県：{{addressData['address1']}}</p>
      <p>住所1：{{addressData['address2']}}</p>
      <p>住所2：{{addressData['address3']}}</p>
  </section>
</template>

<script>
const axios = require('axios');

let url = 'http://zipcloud.ibsnet.co.jp/api/search?zipcode=';

export default {
  data() {
    return {
      zipcode: '',
      addressData: {},
      message: ''
    }
  },
  methods: {
    searchAddressInfo() {
      axios.get(url + this.zipcode).then((res) => {
        if(res.data.results == null) {
          this.message = 'no data'
          return;
        }
        this.addressData = res.data.results[0];
      }).catch((error) => {
        this.message = 'error'
      })
    }
  }
}
</script>
```

例えば変数urlの適当な1文字を削除して、正しい郵便番号を入力して`error`と表示されていればOKです。

## おまけ：入力欄をクリックすると初期の画面に戻す
最後に、住所データが表示されている状態で入力欄をクリックした時に、初期の画面に戻すようにします。

```pages/index.vue
<template>
  <section class="container">
    <p>{{message}}</p>
    <input type="text" placeholder="郵便番号を入力" v-model="zipcode" @focus="focus"/>
    <button @click="searchAddressInfo">住所自動入力</button>
      <p>都道府県：{{addressData['address1']}}</p>
      <p>住所1：{{addressData['address2']}}</p>
      <p>住所2：{{addressData['address3']}}</p>
  </section>
</template>

<script>
const axios = require('axios');

let url = 'http://zipcloud.ibsnet.co.jp/api/search?zipcode=';

export default {
  data() {
    return {
      zipcode: '',
      addressData: {},
      message: ''
    }
  },
  methods: {
    searchAddressInfo() {
      axios.get(url + this.zipcode).then((res) => {
        if(res.data.results == null) {
          this.message = 'no data'
          return;
        }
        this.addressData = res.data.results[0];
      }).catch((error) => {
        this.message = 'error'
      })
    },
    focus() {
      this.zipcode = ''
      this.addressData = {}
      this.message = ''
    }
  }
}
</script>
```

inputに`@focus`を追加し、入力欄をクリックした時に`focus`メソッドを呼び出し、
dataプロパティをすべて初期化します。
住所データが表示されている状態で入力欄をクリックしてみて、初期の画面に戻っていればOKです。


# おわりに
引き続きNuxt.jsなどの勉強がんばりたいです。
もし間違った記載などあればご指摘いただけると幸いです。



# 参考
・<a href="https://www.amazon.co.jp/Vue-js-Nuxt-js%E8%B6%85%E5%85%A5%E9%96%80-%E6%8E%8C%E7%94%B0%E6%B4%A5%E8%80%B6%E4%B9%83/dp/4798056596">Vue.js & Nuxt.js超入門</a>
・<a href="https://qiita.com/ayapon/items/d93807e7699434279531">【Nuxt.js】todoアプリを作成してみた①</a>
・<a href="https://qiita.com/ayapon/items/31b44a43cac73f29f811">【Nuxt.js】todoアプリを作成してみた②</a>
・<a href="https://qiita.com/klme_u6/items/ea155f82cbe44d6f5d88">JavaScriptの非同期処理を並列処理と勘違いしていませんか？</a>
・<a href="https://qiita.com/soarflat/items/1a9613e023200bbebcb3">async/await 入門（JavaScript）</a>
・<a href="https://qiita.com/growsic/items/a919a7e2a665557d9cf4">【解説付き】chromeでXMLHttpRequestをローカルのファイルで行う方法</a>
・<a href="http://zipcloud.ibsnet.co.jp/doc/api">郵便番号検索API</a>