1. 程式人生 > 實用技巧 >38、如何實現跨元件 v-model(雙向資料繫結)?

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隱式新增valueprop,子元件通過props.value接收值。

3. 子元件通過this.$emit('input'),改變父元件v-model繫結的值。

4.v-model可以實現雙向繫結,無需定義接收事件。

為什麼v-model可以實現雙向繫結?

v-model其實是一個語法糖,以下是經過 vue 轉換後的模板:

  1. <input v-model="message" />
  2. // 轉換後:
  3. <input
  4. v-bind:value="message"
  5. v-on:input="message=$event.target.value"


1. 新增v-bind:value

2. 新增v-on:input

的自定義事件。

動態綁定了inputvalue指向了messgae變數,並且在觸發input事件的時候去動態把message設定為目標值,這樣實際上就完成了資料雙向繫結。

雙向繫結其實就是普通單向繫結和事件組合來完成的。

基本模型:父元件 - 當前元件(轉發引數,高階元件) - 子元件

父級元件不用過多解釋,使用v-model傳參。

  1. <template>
  2. <div>
  3. <!-- $attrs & observer -->
  4. <BaseInputAttrs v-model="pModel"/> <br>
  5. <!-- watch & data & emit('input') -->
  6. <BaseInputWatch v-model="pModel"/> <br>
  7. <!-- computed & emit('input') -->
  8. <BaseInputComputed v-model="pModel"/>
  9. </div>
  10. </template>
  11. <script>
  12. export default {
  13. data () {
  14. return {
  15. pModel: 'v-model雙向繫結',
  16. }
  17. },
  18. }
  19. </script>

下面是三種<當前元件>的 v-model 實現方案

方案1:

  1. <template>
  2. <input type="text" v-bind="$attrs" v-on="$listeners">
  3. </template>

$attr: 向子元件傳遞,當前元件沒有接收的,父元件傳遞下來的 prop 。

$listeners: 向父元件傳遞,當前元件沒有接收的,子元件丟擲的自定義事件。

這樣實現後,發現輸入後會變成一個[object InputEvent]的事件物件。

是因為v-on="input"是內建事件,vue 判斷當前元件tag'input'類的表單元素,才會關聯value

當前元件經過封裝後,tag已經改變,父元件實際處理的是this.$emit('input', el)的傳參,el就是[object InputEvent]表單物件。

解決方案:攔截內建的input事件,改變引用,向父元件傳遞最終值,如下。

  1. <template>
  2. <input type="text" v-bind="$attrs" @input="observerInput">
  3. </template>
  4. <script>
  5. export default {
  6. methods: {
  7. // 攔截內建事件
  8. observerInput (el) {
  9. this.$emit('input', el.target.value)
  10. },
  11. },
  12. }
  13. </script>

方案2:watch監聽 和 過渡屬性

分別監聽父元件子元件,通過過渡屬性model儲存值

  1. <template>
  2. <input type="text" v-model="model">
  3. </template>
  4. <script>
  5. export default {
  6. props: {
  7. value: {
  8. type: String,
  9. default: '',
  10. },
  11. },
  12. data () {
  13. return {
  14. model: '',
  15. }
  16. },
  17. watch: {
  18. value: {
  19. immediate: true,
  20. handler (newVal) {
  21. this.model = newVal
  22. },
  23. },
  24. model (newVal) {
  25. this.$emit('input', newVal)
  26. },
  27. },
  28. }
  29. </script>

方案3:計算屬性 setter getter

尤雨溪的方案setter去攔截修改

  1. <template>
  2. <input type="text" v-model="model">
  3. </template>
  4. <script>
  5. export default {
  6. props: {
  7. value: {
  8. type: String,
  9. default: '',
  10. },
  11. },
  12. computed: {
  13. model: {
  14. get () {
  15. return this.value
  16. },
  17. set (newVal) {
  18. this.$emit('input', newVal)
  19. },
  20. },
  21. },
  22. }
  23. </script>

總結:三種方案都是通過this.$emit('input')修改最終的值,這是封裝元件的關鍵所在:統一資料來源。