38、如何實現跨元件 v-model(雙向資料繫結)?
https://blog.csdn.net/qq_39624107/article/details/91452098
為什麼封裝元件要使用雙向繫結?
雙向繫結把資料變更的操作隱藏在元件內部,呼叫者並不會直接感知,業務層無需關心內部實現邏輯,簡化大量與業務無關的程式碼。
元件雙向繫結應有以下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')
修改最終的值,這是封裝元件的關鍵所在:統一資料來源。