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

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

# 前提

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

# 想定

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

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

```ts
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キーを連打します。

```bash
vue init webpack hoge
cd hoge
yarn install
```

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

```bash
yarn run dev
```

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

# 実装

[公式のガイド](https://vuejs.org/v2/guide/forms.html)にはこんな感じでやってね、って書いてあります。

```jinja
<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から取ってきた値を模擬しています。

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

```json:static/companies/1.json
{
  "company": {
    "id": 1,
    "area_id": 1
  }
}
```

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

```jinja: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-model` が `undefined` になってしまう。

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

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

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

```js
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` 使う

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

[/追記]

複数の選択肢があるなら

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


# 考察

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

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

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