點燈坊

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

使用 defineModel() 定義 v-model

Sam Xiao's Avatar 2024-02-20

defineModel() 為 Vue 3.4 的新 API,針對 Two-way Binding 的 v-model 有更簡單的寫法。

Version

Vue 3.4

defineProps()

definemodel001

  • + 寫在 component 內部
  • Component 外的 external state 與 component 內的 internal state 連動

App.vue

<template>
  <div>External state: {{ count }}</div>
  <MyCounter v-model="count"></MyCounter>
</template>

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

let count = ref(0)
</script>

Line 2

<div>External state: {{ count }}</div>
  • 顯示 component 外部的 state

Line 3

<MyCounter v-model="count"></MyCounter>
  • <MyCounter>:將 counter 抽成 MyComponent component
  • v-model:傳入雙向的 property,count state 將傳入 MyCount component,且 MyCount 也會將內部 state 傳出至 count state 傳出

Line 8

import MyCounter from '@/components/MyCounter.vue'
  • import:引用 MyCounter component

Line 10

let count = ref(0)
  • count state:定義 count state 的初始值

MyCounter.vue

<template>
  <div class="box">
    <button @click="onClick">inner +</button>
    <span>Internal state: {{ count }}</span>
  </div>
</template>

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

let props = defineProps({ modelValue: Number })
let emit = defineEmits(['update:modelValue'])

let count = computed({
  get() {
    return props.modelValue
  },
  set(newValue) {
    emit('update:modelValue', newValue)
  }
})

let onClick = () => count.value++
</script>

<style scoped>
.box {
  border-style: solid;
  border-width: 2px;
  border-color: red;
  width: fit-content;
}
</style>
  • 建立 MyCounter component

Line 2

<div class="box">
  <button @click="onClick">inner +</button>
  <span>Internal state: {{ count }}</span>
</div>
  • 將 HTML 部分搬進 MyCounter component
  • box :CSS 只是為了顯示紅框方便識別

Line 11

let props = defineProps({ modelValue: Number })
  • defineProps():定義 component 的 prop,因為 v-model 為特殊 prop,其 prop 名稱固定為 modelValue,並宣告其型別為 Number
  • defineProps(): 回傳為 props Object,將以 props 讀取 prop

defineProps() 為 compiler macro,不需 import,Vue compiler 會自動展開

Line 12

let emit = defineEmits(['update:modelValue'])
  • defineEmits():定義 component 的 event,傳入為 Array,因為 v-model 為特殊的 prop,其 event 固定為 update:modelValue
  • defineEmits() 回傳為 function,將以 emit() 發出 event

defineEmits() 也是 compiler macro

Line 14

let count = computed({
  get() {
    return props.modelValue
  },
  set(newValue) {
    emit('update:modelValue', newValue)
  }
})
  • 建立可讀可寫的 count computed,如此才能對 count computed 遞增
  • Getter:讀取 count computed 時,直接讀取 modelValue prop
  • Setter:對 count computed 修改時,直接將 寫入值 newValue 發出 update:modelValue event 傳出

Line 23

let onClick = () => count.value++
  • 因為 count computed 可讀可寫,因此可當成一般 state 讀取寫入

defineModel()

App.vue

<template>
  <div>External state: {{ count }}</div>
  <MyCounter v-model="count"></MyCounter>
</template>

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

let count = ref(0)
</script>
  • App.vue 部分完全不變

MyCounter.vue

<template>
  <div class="box">
    <button @click="onClick">inner +</button>
    <span>Internal state: {{ count }}</span>
  </div>
</template>

<script setup>
let count = defineModel()
let onClick = () => count.value++
</script>

<style scoped>
.box {
  border-style: solid;
  border-width: 2px;
  border-color: red;
  width: fit-content;
}
</style>

Line 9

let count = defineModel()
  • Vue 3.4 提供了新的 defineModel(),Vue compiler 會自動幫我們展開 defineProps()defineEmits()computed(),使用上只需將原本的 ref() 改成 defineModel() 即可,非常方便

defineModel() 也是 compiler macro

Conclusion

  • Vue 不允許我們直接修改 prop,只能包成可讀可寫的 computed 後才能修改
  • computed() 傳入 function 時只能 唯讀,當傳入有 getter 與 setter 的 Object 才是可讀可寫的 computed
  • 跟 component 有關的 define 系列都是 compiler macro,只能用在 Script Setup 內,且目前只有 Vue 能提供 compiler macro,我們無法自行定義 compiler macro
  • 將原本代碼抽成使用 v-model 的 component 變得非常簡單,只需將 ref() 改成 defineModel() 即可