v-model
是 Vue 中使用頻率特別高的一個指令,而 Vue3 中的 v-model
有了很大的變化,本文將詳細講述一下 Vue2 和 Vue3 中的 v-model
的區別。javascript
若是對 Vue2 中的語法很熟悉,這部分能夠不看。html
首先來回顧一下 Vue2 中的 v-model
,它主要用於表單元素和自定義組件上。v-model
本質上是一個語法糖,會對用戶的輸入作一些特殊處理以達到更新數據,而所謂的處理其實就是給使用的元素默認綁定屬性和事件。java
當 v-model
使用在表單元素上時,會根據元素的不一樣而採用不一樣的處理:api
<input type="text">文本
和 <textarea>
上使用時,會默認給元素綁定名爲 value
的 prop 和名爲 input
的事件;<input type="checkbox">複選框
和 <input type="radio">單選框
上使用時,會默認綁定名爲 checked
的 prop 和名爲 change
的事件;<select>選擇框
上使用時,則綁定名爲 value
的 prop 和名爲 change
的事件。這些是 Vue 默認幫咱們處理的,能夠直接使用。可是你也會發現一些第三方組件也能夠使用 v-model
,好比 Element 中的 Input
組件。這是由於這些組件本身實現了 v-model
,原理其實就是上面說到的綁定屬性和事件。markdown
咱們能夠嘗試實現一下 v-model
,來開發一個簡單的輸入組件,就叫 MyInput
吧:ui
<!-- MyInput 組件代碼 -->
<template>
<div>
<input type="text" :value="value" @input="$emit('input',$event.target.value)">
</div>
</template>
<script> export default { props: { value: String, // 默認接收一個名爲 value 的 prop } } </script>
複製代碼
上面代碼就實現了組件的 v-model
功能,當在這個組件上使用 v-model
時:this
<my-input v-model="msg"></my-input>
複製代碼
其實就等同於:spa
<my-input :value="msg" @input="msg = $event">
複製代碼
Vue 還提供了 model
選項,用於將屬性或事件名稱改成其餘名稱,好比上面的 MyInput
組件,咱們改一下:code
<template>
<div>
<input type="text" :value="title" @input="$emit('change', $event.target.value)" />
</div>
</template>
<script> export default { model: { prop: "title", // 將默認的 prop 名 value 改成 title event: "change", // 將默認的事件名 input 改成 change }, props: { title: String, // 注意 template 代碼中也要修改成 title }, }; </script>
複製代碼
此時使用組件:component
<my-input v-model="msg"></my-input>
// 等同於
<my-input :title="msg" @change="msg = $event"></my-input>
複製代碼
Vue 提供一個 .sync
的修飾符,效果跟 v-model
同樣,也是便於子組件數據更改後自動更新父組件相關數據。實現 .sync
的方式與實現 v-model
殊途同歸,區別就是拋出的事件名須要是 update:myPropName
的結構。
仍是拿上面的 MyInput
說明,咱們仍是傳入一個 title
的 prop,同時組件內部拋出 update:title
事件,代碼以下:
// MyInput 組件中,修改拋出的事件名爲 update:title
<input type="text" :value="title" @input="$emit('update:title', $event.target.value)" />
複製代碼
此時若是使用這個組件,正常應該是這樣:
<my-input :title="msg" @update:title="msg = $event"></my-input>
複製代碼
但此時能夠使用 .sync
修飾符來簡化:
<my-input :title.sync="msg"></my-input>
複製代碼
能夠看到 .sync
和 v-model
所能達到的效果是同樣的,用什麼就看你什麼場景,通常表單組件上都是用 v-model
。
上面說了那麼多,爲的就是接下來區別出 Vue3 中 v-model
帶來的變化,主要變化有如下幾處:
當用在自定義組件上時,v-model
默認綁定的 prop 名從 value
變爲 modelValue
,而事件名也從默認的input
改成 update:modelValue
。在 Vue3 中編寫上面那個 MyInput
組件時,就須要這樣:
<!-- MyInput 組件代碼 Vue3 版 -->
<template>
<div>
<input type="text" :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" // 事件名改成 update:modelValue />
</div>
</template>
<script> export default { props: { modelValue: String, // 默認 prop 從 value 改成 modelValue }, }; </script>
複製代碼
使用組件時:
<my-input v-model="msg"></my-input>
// 等同於
<my-input :modelValue="msg" @update:modelValue="msg = $event"></my-input>
複製代碼
Vue3 中移除了 model
選項,這樣就不能夠在組件內修改默認 prop 名了。如今有一種更簡單的方式,就是直接在 v-model
後面傳遞要修改的 prop 名:
// 要修改默認 prop 名,只需在 v-model 後面接上 :propName,例如修改成 title
<my-input v-model:title="msg"></my-input>
// 等同於
<my-input :title="msg" @update:title="msg = $event"></my-input>
複製代碼
注意組件內部也要修改 props:
<template>
<div>
<input type="text" :value="title" @input="$emit('update:title', $event.target.value)" />
</div>
</template>
<script> export default { // 此時這裏不須要 model 選項來修改了 props: { title: String, // 修改成 title,注意 template 中也要修改 }, }; </script>
複製代碼
同時,.sync
修飾符也被移除了,若是你嘗試使用它,會報這樣的錯誤:
'.sync' modifier on 'v-bind' directive is deprecated. Use 'v-model:propName' instead
錯誤提示中說明了,能夠使用 v-model:propName
的方式來替代 .sync
,由於本質上效果是同樣的。
Vue3 中支持使用多個 v-model
,屬於新增功能,我很喜歡這個功能,使得組件數據更新更靈活。例若有這樣一個表單子組件,用戶輸入的多個數據都須要更新到父組件中顯示,能夠這樣寫:
<!-- 表單子組件 Form -->
<template>
<div class="form">
<label for="name">姓名</label>
<input id="name" type="text" :value="name" @input="$emit('update:name',$event.target.value)">
<label for="address">地址</label>
<input id="address" type="text" :value="address" @input="$emit('update:address',$event.target.value)">
</div>
</template>
<script> export default { props:{ name: String, address: String } } </script>
複製代碼
父組件使用這個組件時:
<child-component v-model:name="name" v-model:address="address"></child-component>
// 將用戶輸入數據更新到父組件中顯示
<p>{{name}}</p>
<p>{{address}}</p>
複製代碼
在 Vue2 中的 v-model
上,咱們用過 .trim
、.lazy
和 .number
這三個內置修飾符,而 Vue3 則在這個基礎上增長了自定義修飾符,即開發者能夠自定義修飾符,以按需處理綁定值。
當咱們在 v-model
後面加上自定義修飾符後,會經過名爲 modelModifiers
的 prop 傳遞給子組件,子組件拿到這個修飾符名後,根據條件修改綁定值。咱們來看一個例子,自定義一個修飾符 capitalize
,用於將輸入字符串的首字母大寫。
假設自定義組件仍是叫 MyInput
,使用 v-model
時加上自定義修飾符 capitalize
:
<my-input v-model.capitalize="msg"></my-input>
複製代碼
因爲不是內置修飾符,因此須要咱們本身在組件內部處理修飾符邏輯,編寫組件:
<!-- MyInput 組件 -->
<template>
<div>
<input type="text" :value="modelValue" @input="emitValue" />
</div>
</template>
<script> export default { props: { modelValue: String, modelModifiers: { // 自定義修飾符會默認傳入這個 prop 中 type: Object, default: () => ({}), }, }, mounted() { // 當組件 v-model 後面加上了自定義修飾符,組件內部會在 modelModifiers 上獲取到修飾符狀態 console.log(this.modelModifiers); // {capitalize: true} }, methods: { emitValue(e) { let value = e.target.value; // 若是使用了自定義修飾符,即狀態爲 true,就處理值 if (this.modelModifiers.capitalize) { value = value.charAt(0).toUpperCase() + value.slice(1); } // emit value this.$emit("update:modelValue", value); }, }, }; </script>
複製代碼
這樣就完成了一個將輸入字符串首字母大寫的v-model
修飾符。
若是是 v-model
帶上了參數,同時使用了自定義修飾符,好比這樣:
<my-input v-model:title.capitalize="msg"></my-input>
複製代碼
那麼傳入組件內部的 prop 就再也不是 modelModifiers
了,而是 titleModifiers
。它的格式是 arg + 'Modifiers'
。此時這個組件應該這樣寫:
<!-- MyInput 組件 -->
<template>
<div>
<input type="text" :value="title" @input="emitValue" />
</div>
</template>
<script> export default { props: { title: String, // modelValue -> title titleModifiers: { // modelModifiers -> titleModifiers type: Object, default: () => ({}), }, }, mounted() { console.log(this.titleModifiers); // {capitalize: true} }, methods: { emitValue(e) { let value = e.target.value; // 若是使用了自定義修飾符,就處理值 if (this.titleModifiers.capitalize) { value = value.charAt(0).toUpperCase() + value.slice(1); } // emit value this.$emit("update:title", value); }, }, }; </script>
複製代碼
以上就是 v-model
在 Vue2 和 Vue3 中的區別表現,我我的以爲新版中使用邏輯更清晰了。只是有些部分再也不兼容了,若是是直接將 Vue2 版本代碼移植到 Vue3 項目中,須要注意 v-model
的實現差別,還有注意要將 .sync
修飾符替換成新寫法。
文中若有不當之處,歡迎評論區指出。