Nuxt(Vue).jsで郵便番号が入力されたら自動的に住所も入力されるフォームを作る

こんにちは、あかしぃです。

Webサービスの会員登録フォームで、郵便番号を入力したら自動的に住所も入力される…みたいなフォームを見たことがあると思います。

Vue.jsやNuxt.jsならこういったフォームを簡単に作れるので、コードを含めて解説します。

自動的に住所が入力されるフォームの作り方

まずソースコードのサンプルがこちら。住所取得apiには「zipcloud」を利用しています。

<template>
  // 郵便番号の入力フォーム
  <input
    v-model="zipcode"
    type="number"
    placeholder="0009999"
    @blur="fetchAddress" />

  // 都道府県入力フォーム(セレクトボックスでも可)
  <input
    v-model="prefecture"
    type="text"
    placeholder="東京都" />

  // 都道府県以下の住所入力フォーム
  <input
    v-model="address"
    type="text"
    placeholder="渋谷区xxxxxx" />
</template>

<script>
import axiosJsonpAdapter from 'axios-jsonp'

const ZIPCODE_API_URL = 'http://zipcloud.ibsnet.co.jp/api/search'

export default {
  data() {
    return {
      zipcode: '',
      prefecture: '',
      address: ''
    }
  },

  methods: {
    async fetchAddress() {
      // 郵便番号のバリデーションチェック
      const reg = /^\d{7}$/
      if (!reg.test(this.zipcode)) return

      // 住所apiを叩く
      const res = await this.$axios.$get(ZIPCODE_API_URL, {
        adapter: axiosJsonpAdapter,
        params: {
          zipcode: this.zipcode
        }
      })

      // 存在しない郵便番号でapiを叩くと200以外のステータスが返ってくる
      if (res.status !== 200) return

      // 返却されたデータを挿入する
      this.prefecture = res.results[0].address1
      this.address = res.results[0].address2 + res.results[0].address3
    }
  }
}
</script>

コードについて、簡単に解説します。

入力フォームについて

入力フォームそのものはただのHTMLなので、とくに解説する点はありません。

v-modelでそれぞれのデータをバインディングしているのと、@blurで入力フォームからフォーカスが外れたタイミングでfetchAddressが実行されるようにしています。

サンプルコードでは都道府県の部分もinputタグを使っていますが、セレクトボックス形式にして一覧から都道府県を選択できるようにしてあげたほうがユーザーにとっては使いやすいかも(どうせ自動入力されるのであまり意味はないかもですが・・・)

セレクトボックスにする場合は、prefecture.jsonといった名前で都道府県一覧のデータをリポジトリに含め、それをvuex(store)に登録すると使いやすいです。使い方のイメージはこんな感じ。

mapGettersなどで、prefecturesで都道府県.jsonの中身を取得できるようにしている前提

<select
  v-model="prefecture"
  placeholder="都道府県を選択">
  <option
    v-for="(prefecture, index) in prefectures"
    :key="index"
    :label="prefecture.name"
    :value="prefecture.name" />
</select>

 

住所自動入力のメソッド部分について

住所の自動入力については、fetchAddressメソッドが担当しています。

const reg = /^\d{7}$/
if (!reg.test(this.zipcode)) return

// if (!/^\d{7}$/.test(this.zipcode)) return と書くと1行でも書ける

この部分は郵便番号のバリデーションです。この処理がなくても問題ありませんが、無意味なapiリクエストを送りたくないので、郵便番号ではない形式の入力があった場合は、この時点で処理を中断しています。

ハイフンつきを許すなら、正規表現部分を少し変更する必要があります。

const res = await this.$axios.$get(ZIPCODE_API_URL, {
  adapter: axiosJsonpAdapter,
  params: {
    zipcode: this.zipcode
  }
})

外部apiからデータを取得する処理です。

zipcloudのリファレンスを読むと、データを取得するには「http://zipcloud.ibsnet.co.jp/api/search/」にzipcode=xxxxxxxというクエリをつけてGETメソッドで叩けばいいことがわかります。

なので、「this.$axios.$get(`ZIPCODE_API_URL?${this.zipcode}`)」のような形式でリクエストを送ればいい…予定だったのですが、zipcloudにはCROS対策が行われていないようです。

なので、別ドメインからapiを叩こうとするとCROSエラーが発生します。

こういったケースではJsonpを使います。(Jsonpについてはこちらの記事とかがわかりやすい)

axiosでJsonpリクエストを送るには、「axios-jsonp」というライブラリが必要です。サンプルコードの先頭で、「import axiosJsonpAdapter from 'axios-jsonp'」としている部分ですね。(先にnpm i axons-jsonpしておきましょう)

こいつをaxiosのオプションとしてセットしてあげれば、無事CROSエラーを回避して、データを取得することができます。

ここでもう一度、zipcloudのリファレンスを見て、返ってくるデータ構造を確認すると、以下のような構造であることがわかります。

{
  "message": null,
  "results": [
    {
      "address1": "北海道",
      "address2": "美唄市",
      "address3": "上美唄町協和",
      // 以下にデータが続く
    }
  ]
}

返ってくるデータ構造がわかれば、あとはそれに対応した処理を書くだけです。

// 存在しない郵便番号でapiを叩くと200以外のステータスが返ってくる
if (res.status !== 200) return

// 返却されたデータを挿入する
this.prefecture = res.results[0].address1
this.address = res.results[0].address2 + res.results[0].address3

prefecture、addressはそれぞれinputタグにバインディングされているので、データを入れてあげればブラウザにも反映され、ユーザーから自動的に住所が入力されたように見える…というわけですね。

まとめ

自分で実装するまでは、「これどうやって作ってんのかな〜…」みたいに思っていましたが、書いてみると意外にシンプルでした。そして、一般的な会員登録フォームではもはやデフォルトの仕様になっている気がしますね。

April 13, 2019 - posted by akashixi