點燈坊

失くすものさえない今が強くなるチャンスよ

使用 v-model 將原生 HTML Element 包成 Component

Sam Xiao's Avatar 2024-02-20

針對一些 HTML Element 的共用 CSS Style,Vue 並不鼓勵使用 Global CSS,而是建議這些 HTML Element 包成 Component,然後各 page 都使用此 Component,此時可在 Component 使用 v-model,讓使用體驗與原生 HTML Element 無異。

Version

Vue 3.4

Common Style

select001

  • 為了風格統一,整個網站的 Select 都要使用此 style

app.vue

<template>
  <select v-model="selected" class="minimal">
    <option disabled value="">Please select one</option>
    <option v-for="(item, i) in options" :value="item.value" :key="i">
      {{ item.text }}
    </option>
  </select>
  {{ selected }}
</template>

<script setup>
import { ref } from 'vue'

let selected = ref('')
let options = ref([
  { text: 'Apple', value: '0' },
  { text: 'Google', value: '1' },
  { text: 'Microsoft', value: '2' }
])
</script>

<style scoped>
select {
  /* styling */
  background-color: white;
  border: thin solid blue;
  border-radius: 4px;
  display: inline-block;
  font: inherit;
  line-height: 1.5em;
  padding: 0.5em 3.5em 0.5em 1em;

  /* reset */
  margin: 0;
  -webkit-box-sizing: border-box;
  -moz-box-sizing: border-box;
  box-sizing: border-box;
  -webkit-appearance: none;
  -moz-appearance: none;
}

select.minimal {
  background-image: linear-gradient(45deg, transparent 50%, gray 50%),
    linear-gradient(135deg, gray 50%, transparent 50%), linear-gradient(to right, #ccc, #ccc);
  background-position:
    calc(100% - 20px) calc(1em + 2px),
    calc(100% - 15px) calc(1em + 2px),
    calc(100% - 2.5em) 0.5em;
  background-size:
    5px 5px,
    5px 5px,
    1px 1.5em;
  background-repeat: no-repeat;
}

select.minimal:focus {
  background-image: linear-gradient(45deg, green 50%, transparent 50%),
    linear-gradient(135deg, transparent 50%, green 50%), linear-gradient(to right, #ccc, #ccc);
  background-position:
    calc(100% - 15px) 1em,
    calc(100% - 20px) 1em,
    calc(100% - 2.5em) 0.5em;
  background-size:
    5px 5px,
    5px 5px,
    1px 1.5em;
  background-repeat: no-repeat;
  border-color: green;
  outline: 0;
}

select:-moz-focusring {
  color: transparent;
  text-shadow: 0 0 0 #000;
}
</style>

Line 2

<select v-model="selected" class="minimal">
  <option disabled value="">Please select one</option>
  <option v-for="(item, i) in options" :value="item.value" :key="i">
    {{ item.text }}
  </option>
</select>
  • <select> 使用 minimal CSS class 重新定義其 style,並且希望整個網站的 <select> 都使用 minimal CSS class

MySelect

App.vue

<template>
  <MySelect v-model="selected" :options />
  {{ selected }}
</template>

<script setup>
import { ref } from 'vue'
import MySelect from '@/components/MySelect.vue'

let selected = ref('')
let options = ref([
  { text: 'Apple', value: '0' },
  { text: 'Google', value: '1' },
  { text: 'Microsoft', value: '2' }
])
</script>

Line 2

<MySelect v-model="selected" :options />
  • minimal CSS class 包進 MySelect component,將資料以 options prop 傳進去,原本的 selected 仍搭配 v-model

MySelect.vue

<template>
  <select v-model="selected" class="minimal">
    <option disabled value="">Please select one</option>
    <option v-for="(item, i) in props.options" :value="item.value" :key="i">
      {{ item.text }}
    </option>
  </select>
  {{ selected }}
</template>

<script setup>
let selected = defineModel()
let props = defineProps({ options: Array })
</script>

<style scoped>
select {
  /* styling */
  background-color: white;
  border: thin solid blue;
  border-radius: 4px;
  display: inline-block;
  font: inherit;
  line-height: 1.5em;
  padding: 0.5em 3.5em 0.5em 1em;

  /* reset */

  margin: 0;
  -webkit-box-sizing: border-box;
  -moz-box-sizing: border-box;
  box-sizing: border-box;
  -webkit-appearance: none;
  -moz-appearance: none;
}

select.minimal {
  background-image: linear-gradient(45deg, transparent 50%, gray 50%),
    linear-gradient(135deg, gray 50%, transparent 50%), linear-gradient(to right, #ccc, #ccc);
  background-position:
    calc(100% - 20px) calc(1em + 2px),
    calc(100% - 15px) calc(1em + 2px),
    calc(100% - 2.5em) 0.5em;
  background-size:
    5px 5px,
    5px 5px,
    1px 1.5em;
  background-repeat: no-repeat;
}

select.minimal:focus {
  background-image: linear-gradient(45deg, green 50%, transparent 50%),
    linear-gradient(135deg, transparent 50%, green 50%), linear-gradient(to right, #ccc, #ccc);
  background-position:
    calc(100% - 15px) 1em,
    calc(100% - 20px) 1em,
    calc(100% - 2.5em) 0.5em;
  background-size:
    5px 5px,
    5px 5px,
    1px 1.5em;
  background-repeat: no-repeat;
  border-color: green;
  outline: 0;
}

select:-moz-focusring {
  color: transparent;
  text-shadow: 0 0 0 #000;
}
</style>

Line 12

let selected = defineModel()
let props = defineProps({ options: Array })
  • 使用 defineModel() 定義 selected v-model
  • 使用 defineProps() 定義 props prop

Conclusion

  • v-model 特別適合將原本 HTML element 包成 component,因為 HTML element 也使用 v-model,包成 component 改變的代碼不多