セレクトボックスの値を、サーバーサイドから非同期で取得したい。
その場合にちょっとはまったのでメモ。
前提
- 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から取ってきた値を模擬しています。
{
"areas": [
{
"id": 1,
"name": "hokkaido"
},
{
"id": 2,
"name": "aomori"
}
]
}
{
"company": {
"id": 1,
"area_id": 1
}
}
これらを取得してSelectの選択肢にぶち込みます。
<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を返して逐次処理するように調整した方がいいかも。
単純に書くとこんな感じか。
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サーバー側でまとめて返せば並列処理による問題は発生しないから、一つのリクエストで済むようにすべきかもしれない。
リクエスト少ない方が、サーバーサイド側のブートが少ないから速いような気もするし。
サーバーサイドをシンプルにしすぎてフロントがつらくなる、みたいなことは随所で聞くので、寄せるバランスはやっていくうちに模索していく所存。