有人說,這種小白問題,也好意思問?
你別說,我初學vue的時候,可是被這些問題折磨得死去活來的,硬著頭皮照著官網檔案demo寫,會用之後,日常開發也寫出過很多跟 v-model 相關的bug,後來下定決心仔細研究一下之後,才發現這裡面的門道還有點多,且聽我細細講來。【相關推薦:】
先來看解答:
<template> <div class="test-v-model"> <p>使用v-model</p> <input v-model="msg" placeholder="edit me" /> <p>{{ msg }}</p> <p>不使用v-model</p> <input :value="msg1" @input="handleInput" placeholder="edit me" /> <p>{{ msg1 }}</p> </div> </template> <script> export default { name: 'test-v-model', data() { return { msg: '', msg1: '' } }, methods: { handleInput(e) { this.msg1 = e.target.value } } } </script>
不使用 v-model,就需要通過 value 屬性繫結值和 input 事件改變繫結值來實現雙向繫結。
換句話說,v-model只是一種簡寫形式而已
事實上,v-model 的本質就是語法糖,它負責監聽使用者的輸入事件以更新資料,並對一些極端場景進行一些特殊處理。 -- 官方檔案
v-model 在內部為不同的輸入元素使用不同的 property 並丟擲不同的事件:
- text 和 textarea 元素使用
value
property 和input
事件;- checkbox 和 radio 使用
checked
property 和change
事件;- select 欄位將
value
作為 prop 並將change
作為事件。
問:什麼是雙向繫結?
雙向繫結就是當資料變化之後,檢視同步更新,當檢視變化之後,資料也會更新。
問:什麼是單向資料繫結?
單向資料繫結就是當資料變化之後,檢視同步更新,當檢視變化之後,資料不會更新。
在vue中是通過指令 v-model 來實現雙向繫結,通過 v-bind
來實現單向資料繫結
看完下面這段程式碼和這段程式碼執行的gif演示,你就能明白他們的區別了。
<template> <div> <p>雙向繫結</p> <input v-model="msg" placeholder="edit me" /> <p>{{ msg }}</p> <p>單向資料繫結</p> <input v-bind:value="msg1" placeholder="edit me" /> <p>{{ msg1 }}</p> </div> </template> <script> export default { name: 'test-v-model', data() { return { msg: '', msg1: '' } } } </script>
從gif圖中可以看出,使用 v-model,當資料變化之後,檢視同步更新,當檢視變化之後,資料也會更新,這就是雙向繫結。
使用 v-bind,當資料變化之後,檢視同步更新,當檢視變化之後,資料不會更新,這就是單向資料繫結。
問:什麼是 vue 單向資料流?
子元件不能改變父元件傳遞給它的 prop 屬性,推薦的做法是它丟擲事件,通知父元件自行改變繫結的值。
總結起來就是資料向下,事件向上。
vue 檔案在介紹 Prop 時就提出了單向資料流的概念,點選此處 檢視 vue 檔案對單向資料流的說明。
所有的 prop 都使得其父子 prop 之間形成了一個單向下行繫結:父級 prop 的更新會向下流動到子元件中,但是反過來則不行。這樣會防止從子元件意外變更父級元件的狀態,從而導致你的應用的資料流向難以理解。
額外的,每次父級元件發生變更時,子元件中所有的 prop 都將會重新整理為最新的值。這意味著你不應該在一個子元件內部改變 prop。如果你這樣做了,Vue 會在瀏覽器的控制檯中發出警告。
我們看下面這個例子:
子元件直接對 prop 值做雙向繫結,會發生什麼?
父元件程式碼:
<template> <child-component :value="fatherValue" /> </template> <script> import ChildComponent from './child.vue' export default { name: 'father-component', components: { ChildComponent }, data() { return { fatherValue: '' } } } </script>
子元件程式碼:
<template> <div class="child-component"> <input v-model="value" placeholder="edit me" /> <p>{{ value }}</p> </div> </template> <script> export default { name: 'child-component', props: { value: { type: String, default: '' } } } </script>
可以看到,childComponent中的 prop 值可以實現雙向繫結,但是 FatherComponent 中的 data 值並未發生改變,而且控制檯丟擲了警告:
[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "value"
翻譯一下:避免直接改變 prop 值,因為每當父元件重新渲染時,該值將被覆蓋。相反,使用基於 prop 值的 data 或 computed。
很顯然,直接改變子元件的 prop 值的這種行為被 vue 禁止了。
如何操作傳入子元件的 prop 值
但是很多時候,我們確實要操作傳入子元件的 prop 值,該怎麼辦呢?
正如上面的警告所說,有兩種辦法:
props: { initialCounter: { type: Number, default: 0 }, }, data() { return { counter: this.initialCounter } }
props: { size: { type: String, default: '' } }, computed: { normalizedSize: function () { return this.size.trim().toLowerCase() } }
這樣不管怎麼運算元據都是操作的子元件資料了,不會影響到父元件資料。
所以,我們想用 prop 傳入的資料實現雙向繫結,可以這麼寫:
父元件程式碼不變
子元件裡用 innerValue 來接收傳入的 value :
<template> <div class="child-component"> <input v-model="innerValue" placeholder="edit me" /> <p>{{ innerValue }}</p> </div> </template> <script> export default { name: 'child-component', props: { value: { type: String, default: '' } }, data() { return { innerValue: this.value } } } </script>
這裡要注意一個問題
在 JavaScript 中物件和陣列是通過參照傳入的,所以對於一個陣列或物件型別的 prop 來說,在子元件中改變變更這個物件或陣列本身將會影響到父元件的狀態。
還是上面的例子,我們將傳入的值改為物件:
父元件程式碼:
<template> <child-component :obj="fatherObj" /> </template> <script> import ChildComponent from './child.vue' export default { name: 'father-component', components: { ChildComponent }, data() { return { fatherObj: { name: 'lin' } } } } </script>
子元件程式碼:
<template> <div class="child-component"> <input v-model="innerObj.name" placeholder="edit me" /> <p>{{ innerObj.name }}</p> </div> </template> <script> export default { name: 'child-component', props: { obj: { type: Object, default: () => {} } }, data() { return { innerObj: this.obj } } } </script>
這裡的 this.obj
是參照型別,賦值給了 innerObj
,所以 innerObj
實際上還是指向了父元件的資料,對 innerObj.name
的修改依然會影響到父元件
所以,處理這種參照型別資料的時候,需要深拷貝一下
import { clone } from 'xe-utils' export default { name: 'child-component', props: { obj: { type: Object, default: () => {} } }, data() { return { innerObj: clone(this.obj, true) } } }
如上圖所示,這樣子元件和父元件之間的資料就不會互相影響了。
至此,終於把雙向繫結和單向資料流講清楚了,真的沒想到,平時開發時都懂的概念,想講清楚居然花了這麼多篇幅,確實不容易,不過,這也是對自己的一種鍛鍊吧。
問:v-model是雙向繫結嗎?
是,但只是語法糖
問:v-model是單向資料流嗎?
是,資料向下,事件向上
本題還有一些其他問法,比如:
看完本篇文章,相信不管怎麼問,你都能對這兩個概念理解透徹了。
更多程式設計相關知識,請存取:!!
以上就是聊聊Vue中如果不通過v-model實現雙向繫結?的詳細內容,更多請關注TW511.COM其它相關文章!