聊聊Vue中如果不通過v-model實現雙向繫結?

2022-01-29 10:00:22
Vue中如果不通過v-model實現雙向繫結?下面本篇文章給大家介紹一下不使用v-model,實現雙向繫結的方法,希望對大家有所幫助!

不使用v-model,如何實現雙向繫結?

有人說,這種小白問題,也好意思問?

你別說,我初學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 元件之間互動的單向資料流

問:什麼是雙向繫結?

雙向繫結就是當資料變化之後,檢視同步更新,當檢視變化之後,資料也會更新。

問:什麼是單向資料繫結?

單向資料繫結就是當資料變化之後,檢視同步更新,當檢視變化之後,資料不會更新。

在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>

1.gif

從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>

2.png

3.png

4.png

可以看到,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 值,該怎麼辦呢?

正如上面的警告所說,有兩種辦法:

  • 這個 prop 用來傳遞一個初始值,定義一個原生的 data property 並將這個 prop 用作其初始值
props: {
  initialCounter: {
    type: Number,
    default: 0
  },
},
data() {
  return {
    counter: this.initialCounter
  }
}
  • 這個 prop 以一種原始的值傳入且需要進行轉換,用這個 prop 的值來定義一個計算屬性
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>

5.png

6.png

這裡的 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)
    }
  }
}

7.png

如上圖所示,這樣子元件和父元件之間的資料就不會互相影響了。

總結

至此,終於把雙向繫結和單向資料流講清楚了,真的沒想到,平時開發時都懂的概念,想講清楚居然花了這麼多篇幅,確實不容易,不過,這也是對自己的一種鍛鍊吧。

問:v-model是雙向繫結嗎?

是,但只是語法糖

問:v-model是單向資料流嗎?

是,資料向下,事件向上

本題還有一些其他問法,比如:

  • vue 的雙向繫結和單向資料流有什麼區別?
  • 為什麼說 vue 的雙向繫結和單向資料流不衝突?

看完本篇文章,相信不管怎麼問,你都能對這兩個概念理解透徹了。

更多程式設計相關知識,請存取:!!

以上就是聊聊Vue中如果不通過v-model實現雙向繫結?的詳細內容,更多請關注TW511.COM其它相關文章!