前往
大廳
主題

【隨手寫】Vue - Component v-model

wtfgn | 2023-12-05 21:38:45 | 巴幣 1000 | 人氣 60

首先這篇文章只是用來記錄一些我學過的東西,並不代表文章内容必定準確,如果想瞭解得更詳細,建議還是參考官網。
===============================================
首先,如果想綁定input element(e.g. <input/>),讓我們可以進行一個雙向綁定的話,我們通常就用v-model。這樣子就可以做到當作form element的value有改變的時候(用戶輸入),對應的reactive varaible就可以即時更新了。

// App.vue
<template>
  <p>Message is: {{ message }}</p>
  <input v-model="message" placeholder="edit me" />
</template>
<script setup>
import { ref } from 'vue'

const message = ref('')
</script>

不過v-model其實是一種syntax sugar,如果攤開來寫的話,就會變成一下那樣:

<template>
  <p>Message is: {{ message }}</p>
  <input
    placeholder="edit me"
    :value="message"
    @input="event => message = event.target.value"/>
</template>

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

const message = ref('')
</script>

我們看到可以從這個syntax sugar
<input v-model="message"/>
改寫成一下這個樣子,而且功能一樣
<input
   :value="message"
   @input="event => message = event.target.value"
/>

我們首先來翻譯一下成中文,
  • <input/>就是我們有個textbox的element,我們可以普通地在裏面打字
  • :value="message"其實也是一種shorthand,翻譯一下就會變成
    v-bind:value="message"。首先我們要知道這個input element裏面有很多預設屬性,其中一個就是叫"value",而這個"value"就是裝著我們打上去的字,如果"message”裝著"hello world",那"value"這個屬性也就會裝著"hello world"。
    v-bind就是用來綁定"value"這個屬性跟我們的"message"變數,那現在如果我們初始化"message"為"destroy world",那"value"也就會變成"destroy world"了,textbox也為顯示"destroy world"
  • @input其實是一種shorthand,正式來説應該是v-on:input。首先input是一種event,我們打字的時候就會觸發這個event,然後觸發之後就會call後面的function了,也就是"event => message = event.target.value"。
    這裏用到了ES6的新特性——arrow function,在這邊 "=>" 前面的是我們的這個function的parameter,後面就是function body了。
    總括來説,當我們打字的時候,觸發了input的event,而當這個event被觸發了,就會把event.target.value的值assign到我們message這個變數了,也就是把我們打的字放進message裏面了。簡單來説,如果我們在這個textbox裏面打"hello world",然後更新"message",然後因爲message綁定了value這個屬性,所以就會顯示"hello world"。

講完一下基本的東西,就來講一下這篇文章的主題。

其實基本上,自己寫的form component跟原生的input element沒什麽大不同,只是我們用了一個component裝著一個form element而已。用上面的例子來説明的話:
"message": 就是我們想要用來綁定的變數,當input element的value更新的時候,我們也想"message"跟著改變。

假設我們有一個component叫做“MyComponent.vue",裏面裝著一個<input/>,然後在我們的main component裏面,也就是"App.vue",裏面有一個"message”的變數。
目標:雙向綁定"App.vue"的"message"跟"MyComponent.vue"的<input/>

我們現在有幾個問題:
  1. 如何讓"MyComponent.vue"的<input/>看得到"App.vue"的"message"?
  2. 如何讓"MyComponent.vue"的<input/>更新的時候,也可以更新到"App.vue"的"message"?
對第一個問題,就是用props直接傳下去啊,然後如果用戶輸入了東西就用event傳上來,然後用傳上來的東西去更新"message",那就順便解決了第二個問題,完美!
//App.vue
<template>
  其實可以簡化成: <CustomInput v-model="message" /> {{ message }}
  <CustomInput
    
//解決問題一: 我們在這裏傳message下去一個叫modelValue的props
    :model-value="message"
    // 解決問題二: 如果聽到update event就更新message
    @update:model-value="value => message = value"
    /> {{ message }}
</template>

<script setup>
import { ref } from 'vue'
import CustomInput from './CustomInput.vue'
  
const message = ref('hello')
</script>

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

// MyComponent.vue
<script setup>
// 定義一個叫modelValue的props,用來接受上面傳下來的message
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>

<template>
  <input
    :value="modelValue" //  <--- 爲啥會存在??
    // 解決問題二:
    如果用戶輸入了東西,觸發了"update:modelValue"的event,順便把輸入的東西傳上去
    @input="$emit('update:modelValue', $event.target.value)"
  />
</template>

先捋一捋下思緒,首先我輸入了"abc",在"MyComponent.vue"裏面觸發了"update:modeValue" event,把輸入的東西也就是"abc"傳上去。也就是下面這行:
@input="$emit('update:modelValue', $event.target.value)"

然後上面聽到了這個event,就更新message變數,也就是下面這行做的東西:
@update:model-value="value => message = value"

現在你可能會有些疑問,如果我移除了
:value="modelValue",我再打字,不是會照樣更新嗎?其實你説的沒錯,可是如果我想給一些預設值的話呢,例如我想把textbox的value初始化為"hello world",如果移除掉的話,因爲沒有綁定到modelValue(這個modeLValue會再被上面的"message"綁定"),所以就沒辦法顯示了。而且有時候我們會想清楚textbox裏面的字,如果沒有綁定的話"modelValue"的話,那就沒辦法了。

爲什麽我們一定要用event?直接修改modelValue不就行了嗎,反正都modelValue跟上面的message變數綁定了,如果我改modelValue那message不就會跟著改嗎?對的,可是我們要從上面扔一個props下去讓子組件接,可是在Vue裏面,props是one-way data binding,代表子組件不應該修改上面傳下來的東西,也就是modelValue不應該被修改,不然會導致一些難以debug的data flow問題。


這樣子就做到component間的雙向綁定了,這裏再提供一個寫法,功能也是一樣的

// App.vue
<template>
  <h1>{{ mainTitle }}</h1>
  <MyComponent v-model:title="mainTitle" />
</template>

<script setup>
import { ref } from 'vue'
import MyComponent from './MyComponent.vue'
  
const mainTitle = ref('v-model argument example')
</script>

// MyComponent.vue
<template>
  <input type="text" v-model="model"/>
</template>

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

const props = defineProps(['title'])
const emit = defineEmits(['update:title'])

const model = computed({
  get: () => props.title,
  set: (value) => emit('update:title', value)
})
</script>
+++++++++++++++++++++++++++++++++++
更新:
自Vue3.4以後,一般建議使用defineModel這個macri來去實現component的雙向綁定
請自行參閲官網文件或者defineModel的官方説明

創作回應

leolee1919
https://truth.bahamut.com.tw/s01/202103/3f4b3e146bf9243651e5678eee0db8b9.JPG
2023-12-05 21:57:59

更多創作