爲什麼封裝組件要使用雙向綁定?
雙向綁定把數據變更的操作隱藏在組件內部,調用者並不會直接感知,業務層無需關心內部實現邏輯,簡化大量與業務無關的代碼。
組件雙向綁定應有以下2個特點:
1. 父組件只傳輸 prop
,不定義事件接收。
2. 由子組件更新傳下來的值。
本篇文章詳解如何用 v-model 實現3種雙向綁定
v-model 是什麼?
1. v-model
即可以作用在普通表單元素上,又可以作用在組件上。
2. vuejs隱式添加 value
的 prop
,子組件通過 props.value
接收值。
3. 子組件通過 this.$emit('input')
,改變父組件 v-model
綁定的值。
4. v-model
可以實現雙向綁定,無需定義接收事件。
爲什麼v-model可以實現雙向綁定?
v-model
其實是一個語法糖,以下是經過 vue 轉換後的模板:
<input v-model="message" />
// 轉換後:
<input
v-bind:value="message"
v-on:input="message=$event.target.value"
1. 添加 v-bind:value
。
2. 添加 v-on:input
的自定義事件。
動態綁定了 input
的 value
指向了 messgae
變量,並且在觸發 input
事件的時候去動態把 message
設置爲目標值,這樣實際上就完成了數據雙向綁定。
雙向綁定其實就是普通單向綁定和事件組合來完成的。
基本模型:父組件 - 當前組件(轉發參數,高階組件) - 子組件
父級組件不用過多解釋,使用 v-model
傳參。
<template>
<div>
<!-- $attrs & observer -->
<BaseInputAttrs v-model="pModel"/> <br>
<!-- watch & data & emit('input') -->
<BaseInputWatch v-model="pModel"/> <br>
<!-- computed & emit('input') -->
<BaseInputComputed v-model="pModel"/>
</div>
</template>
<script>
export default {
data () {
return {
pModel: 'v-model雙向綁定',
}
},
}
</script>
下面是三種<當前組件>的 v-model 實現方案
方案1:
<template>
<input type="text" v-bind="$attrs" v-on="$listeners">
</template>
$attr: 向 子組件
傳遞,當前組件
沒有接收的,父組件
傳遞下來的 prop 。
$listeners: 向父組件
傳遞,當前組件
沒有接收的,子組件
拋出的自定義事件。
這樣實現後,發現輸入後會變成一個 [object InputEvent]
的事件對象。
是因爲 v-on="input"
是內置事件,vue 判斷 當前組件
的 tag
是 'input'
類的表單元素,纔會關聯 value
。
當前組件
經過封裝後,tag
已經改變,父組件實際處理的是 this.$emit('input', el)
的傳參,el
就是 [object InputEvent]
表單對象。
解決方案:攔截內置的 input
事件,改變引用,向 父組件
傳遞最終值,如下。
<template>
<input type="text" v-bind="$attrs" @input="observerInput">
</template>
<script>
export default {
methods: {
// 攔截內置事件
observerInput (el) {
this.$emit('input', el.target.value)
},
},
}
</script>
方案2:watch監聽 和 過渡屬性
分別監聽 父組件
和子組件
,通過過渡屬性 model
存儲值
<template>
<input type="text" v-model="model">
</template>
<script>
export default {
props: {
value: {
type: String,
default: '',
},
},
data () {
return {
model: '',
}
},
watch: {
value: {
immediate: true,
handler (newVal) {
this.model = newVal
},
},
model (newVal) {
this.$emit('input', newVal)
},
},
}
</script>
方案3:計算屬性 setter getter
尤雨溪的方案 setter
去攔截修改
<template>
<input type="text" v-model="model">
</template>
<script>
export default {
props: {
value: {
type: String,
default: '',
},
},
computed: {
model: {
get () {
return this.value
},
set (newVal) {
this.$emit('input', newVal)
},
},
},
}
</script>
總結:三種方案都是通過 this.$emit('input')
修改最終的值,這是封裝組件的關鍵所在:統一數據源。
之後幾篇文章會介紹其他的雙向綁定方案!
參考文檔:https://ustbhuangyi.github.io/vue-analysis/extend/v-model.html