多個 Select 連動也是實務上常見需求,傳統 jQuery 必須搭配複雜的 Event 與重組 HTML 才能達成,使用 Vue 的 Reactivity API 將可輕鬆達成需求。
Data
data.json
[
{
"name": "基隆市",
"areas": [
{
"name": "仁愛區",
"zip": "200"
},
{
"name": "信義區",
"zip": "201"
},
{
"name": "中正區",
"zip": "202"
},
{
"name": "中山區",
"zip": "203"
},
{
"name": "安樂區",
"zip": "204"
},
{
"name": "暖暖區",
"zip": "205"
},
{
"name": "七堵區",
"zip": "206"
}
]
},
{
"name": "台北市",
"areas": [
{
"name": "中正區",
"zip": "300"
},
{
"name": "大同區",
"zip": "301"
},
{
"name": "中山區",
"zip": "302"
},
{
"name": "松山區",
"zip": "303"
},
{
"name": "大安區",
"zip": "304"
},
{
"name": "萬華區",
"zip": "305"
},
{
"name": "信義區",
"zip": "306"
},
{
"name": "士林區",
"zip": "307"
},
{
"name": "北投區",
"zip": "308"
},
{
"name": "內湖區",
"zip": "309"
},
{
"name": "南港區",
"zip": "310"
},
{
"name": "文山區",
"zip": "311"
}
]
},
{
"name": "新竹市",
"areas": [
{
"name": "香山區",
"zip": "400"
}
]
}
]
- 儲存所有
都市
、行政區
與郵遞區號
- 將 data 儲存在
.json
並放在assets
目錄下
<template>
<div>
<span>Cities:</span>
<select v-model="cityIndex">
<option disabled value="">Please select one</option>
<option v-for="(item, i) in cities" :value="i" :key="i">
{{ item.name }}
</option>
</select>
</div>
<div>
<span>Areas: </span>
<select v-model="areaIndex">
<option disabled value="">Please select one</option>
<option v-for="(item, i) in areas" :value="i" :key="i">
{{ item.name }}
</option>
</select>
</div>
<div>
<span>Zip: </span>
<span>{{ zip }}</span>
</div>
</template>
<script setup>
import { ref, computed, watch, onMounted } from 'vue'
import source from './assets/data.json'
// Cities
let cities = ref([])
let cityIndex = ref('')
onMounted(() => (cities.value = source))
watch(cityIndex, () => (areaIndex.value = 0))
// Areas
let areaIndex = ref('')
let areas = computed(() => (cityIndex.value === '' ? [] : cities.value[cityIndex.value].areas))
// Zip
let zip = computed(() => (areaIndex.value === '' ? '' : areas.value[areaIndex.value].zip))
</script>
Line 27
import source from './assets/data.json'
- import:從
assets
目錄 import 進 JSON 檔
Line 30
let cities = ref([])
let cityIndex = ref('')
cities
state:提供<option>
所需資料cityIndex
state:提供<select>
的v-model
綁定資料
Line 33
onMounted(() => (cities.value = source))
onMounted()
:在mounted
hook 將 JSON 檔寫入options
state
Line 34
watch(cityIndex, () => (areaIndex.value = 0))
watch()
:追蹤cityIndex
state,當所選的都市
有變化時,立即 reset地區
的areaIndex
,避免新竹市
出現 bug
Line 3
<span>Cities:</span>
<select v-model="cityIndex">
<option disabled value="">Please select one</option>
<option v-for="(item, i) in cities" :value="i" :key="i">
{{ item.name }}
</option>
</select>
- 顯示所有
都市
cityIndex
state:為<select>
的v-model
cities
state:使用v-for
列舉所有都市
Line 37
let areaIndex = ref('')
areaIndex
state:提供<select>
的v-model
綁定資料
Line 38
let areas = computed(() => (cityIndex.value === '' ? [] : cities.value[cityIndex.value].areas))
areas
computed:- 當
cityIndex
state 為 empty string 時,回傳 empty array - 當
cityIndex
state 非 empty string 時,值將以其值取得所有行政區
- 當
Line 12
<span>Areas: </span>
<select v-model="areaIndex">
<option disabled value="">Please select one</option>
<option v-for="(item, i) in areas" :value="i" :key="i">
{{ item.name }}
</option>
</select>
- 顯示所有
行政區
areaIndex
state:為<select>
的v-model
areas
computed:使用v-for
列舉所有行政區
Line 41
let zip = computed(() => (areaIndex.value === '' ? '' : areas.value[areaIndex.value].zip))
zip
computed:- 當
areaIndex
state 為 empty string 時,回傳 empty string - 當
areaIndex
state 非 empty string 時,值將以其值取得郵遞區號
- 當
Line 21
<span>Zip: </span>
<span>{{ zip }}</span>
zip
state:顯示該行政區
的郵遞區號
Conclusion
- 透過 Computed,只要 state 有變動,其對應 Computed 也會變動,讓我們不必再使用 Event 動態產生 HTML
- Watch 適合處理一些特別的 edge case,主要還是盡量以 Computed 實現