LoginSignup
33
36

More than 3 years have passed since last update.

Vue.js 2.2 で、select タグの選択肢を非同期で取得する時のデフォルト値ではまった

Last updated at Posted at 2017-04-01

セレクトボックスの値を、サーバーサイドから非同期で取得したい。
その場合にちょっとはまったのでメモ。

前提

  • Vue.js 2.2.6
  • サーバーサイドは考えない

想定

企業のエリアを選択させるUIを作ろうとしています。
企業のエリアが既に入力されていた場合は、デフォルト値をセットします。

TypeScript で書いたらこんな感じのインターフェイス。

interface Company {
  id: 1
  area_id: 1
}

interface Area {
  id: number
  name: string
}
  • company
  • areas

という2つの情報をサーバーから別URLで取得することになります。
まとめて返さないのはAPIサーバー側の仕様を単純化するためです。

環境構築

vue-cli 便利ですね。まずはこれをインストールします。
ついでにyarnも入れます。速いので。

npm install -g vue-cli yarn

適当にプロジェクトを作ります。
vue init で色々聞かれるのでEnterキーを連打します。

vue init webpack hoge
cd hoge
yarn install

とりあえずコンパイルしておきます。

yarn run dev

ブラウザが立ち上がっているはずです。

実装

公式のガイドにはこんな感じでやってね、って書いてあります。

<template>
  <select v-model="selected">
    <option v-for="option in options" v-bind:value="option.value">
      {{ option.text }}
    </option>
  </select>
  <span>Selected: {{ selected }}</span>
</template>

<script>
  export default {
    data: {
      selected: 'A',
      options: [
        { text: 'One', value: 'A' },
        { text: 'Two', value: 'B' },
        { text: 'Three', value: 'C' }
      ]
    }
  }
</script>

これを真似て、選択肢の取得部分だけ動的にやってみます。

vue-cliで作った webpack のテンプレートでは、 static/data.json というファイルが localhost:8080/static/data.json で見られるようになってます。
これを利用します。

まずは静的なデータを作ります。
サーバーサイドのJSON APIでDBから取ってきた値を模擬しています。

static/areas.json
{
  "areas": [
    {
      "id": 1,
      "name": "hokkaido"
    },
    {
      "id": 2,
      "name": "aomori"
    }
  ]
}
static/companies/1.json
{
  "company": {
    "id": 1,
    "area_id": 1
  }
}

これらを取得してSelectの選択肢にぶち込みます。

src/components/Hello.vue
<template>
  <div class="hello">
    <select v-model="company.area_id">
      <option v-for="area in areas" v-bind:value="area.id">
        {{ area.name }}
      </option>
    </select>
    <span>Selected: {{ company.area_id }}</span>
  </div>
</template>

<script>
  import axios from 'axios'

  export default {
    data () {
      return {
        company: {
          id: 1,
          area_id: 0
        },
        areas: []
      }
    },
    created () {
      axios.get('/static/areas.json').then(res => { this.areas = res.data.areas })
      axios.get('/static/companies/1.json').then(res => { this.company = res.data.company })
    }
  }
</script>

はまったのが、最初、 created()メソッド内で取得する順番を逆にしていたこと。
Selectの選択肢を取得してから、v-model を設定しないと、 v-modelundefined になってしまう。

理由は分からないが、多分Vue.js側がareasの変更を検知して再計算する時に、 v-modelの値を書き換えてしまうのだろう。

本当はサーバーサイドの処理時間によってはタイミングが異なることがあるはずなので、Promiseを返して逐次処理するように調整した方がいいかも。

単純に書くとこんな感じか。

axios.get('/static/areas.json')
  .then(res => { 
    this.areas = res.data.areas
    return axios.get('/static/companies/1.json')
  })
  .then(res => { 
    this.company = res.data.company 
  })

[追記]

2018年なので、今なら普通に async/await 使う

async mounted() {
  this.areas = (await axios.get('/static/areas.json')).data.areas
  this.company = (await axios.get('/static/companies/1.json')).data.company
})

[/追記]

複数の選択肢があるなら

Promise.all([
    axios.get('prefectures'),
    axios.get('areas'),
    axios.get('countries'),
    axios.get('markets'),
]).then(results => {
    this.prefectures = results[0]
    // ...
})

考察

画面にべったり沿ったAPIにしたくないから、できるのであればJSで並列取得したい。サーバーサイドはRESTにしたい。

しかし、APIサーバー側でまとめて返せば並列処理による問題は発生しないから、一つのリクエストで済むようにすべきかもしれない。
リクエスト少ない方が、サーバーサイド側のブートが少ないから速いような気もするし。

サーバーサイドをシンプルにしすぎてフロントがつらくなる、みたいなことは随所で聞くので、寄せるバランスはやっていくうちに模索していく所存。

33
36
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
33
36