1. 程式人生 > >.sync 王者回歸,v-model 使命將至

.sync 王者回歸,v-model 使命將至

果只是簡單用來資料傳遞改變的話.sync和v-model再適合不過了。如果用過1.0的 Vue 的開發者,我相信 .sync 會讓你用起來非常便捷,通過雙向繫結很簡單就能實雙,父子元件的雙向繫結,2.0為了保持單向資料流的良好性,去除了 .sync 的功能。

官方解釋:

1.0 Props 現在只能單向傳遞。為了對父元件產生反向影響,子元件需要顯式地傳遞一個事件而不是依賴於隱式地雙向繫結。

推薦使用

自定義元件事件
自定義輸入元件 (使用元件事件)
全域性狀態管理
通過大量觀察,在初期2.0版本中,因為 .sync 並沒有迴歸,只是在2.3進行迴歸,在元件庫中進行資料雙向繫結,幾乎都是通過 v-model 來進行的。但是無論從語意上還是感觀上,給程式碼維護的感就是不直觀,v-model 在開發通常都是結合 Input 輸入框來結合進行一個數據繫結,進行父子元件雙向繫結,但是相比自定義 v-on 元件事件,無論從程式碼量,還是用法上更加簡潔。

在 Vue 中,有許多方法和 Angular 相似,這主要是因為 Angular 是 Vue 早期開發的靈感來源。然而 Angular 中存在許多問題,在 Vue 中已經得到解決。

官方解釋

自定義事件可以用來建立自定義的表單輸入元件,使用 v-model 來進行資料雙向繫結。

<input v-model="something">

這不過是以下示例的語法糖:

<input
  v-bind:value="something"
  v-on:input="something = $event.target.value">

v-model 其實也是一個語法糖,想要理解這些程式碼,你要先知道Input元素上本身有個oninput事件,這是HTML5新增加的,類似 onchange,每當輸入框內容發生變化的時候,就會觸發Input事件,然後把 Input 輸入框中 value 值再次傳遞給 something。

此時 value 運用在一個 Input 元素上,用:v-bind:value=‘something’,意義上面只是把 Input 輸入框中的 value 值與 something 作為一一對應的雙向繫結,這就像一個迴圈操作,當再次觸發 Input 事件時,input($event.target)物件中的value值會再次改變something。

這裡我們對 v-model 繫結在 Input 元素上進行語法糖上的解析。

既然在元素上能進行雙向繫結,那在元件中進行雙向繫結又如何實現,原理其實都是一樣的,只是應用在自定義的元件上時,拿的並不是$event.target.value,因為我此時不作用在 Input 輸入框上。

在元件中使用時,它相當於下面的簡寫:

<custom-input
  v-bind:value="something"
  v-on:input="something = arguments[0]">
</custom-input>

通過以上簡寫,通過自定事件讓 v-model 進行一個父子元件雙向繫結的話。

v-bind:value=‘something’ 此時 value 是作為子元件接收的 Props
接收的只能是 value 嗎?必須是,因為 v-model 是基於 Input 輸入框定製的,其中value 值是為 Input 內部定製的

v-on:input="something = arguments[0]"

此時作用在元件上時,v-on 監聽的語法糖也會有所改動,監聽的並不是$event.target.value,而是回撥函式中的第一個引數。

父元件

<template>
  <div class="hello">
    <button @click="show=true">開啟model</button>
    <demo v-model="show"></demo>
  </div>
</template>

<script>
import Demo from './Demo.vue'
export default {
  name: 'hello',
  components: {
    Demo
  },
  data () {
    return {
      show: false
    }
  }
}
</script>

子元件

<template>
   <div v-show="value">
      <div>
         <p>這是一個Model框</p>
         <button @click="close">關閉model</button>
      </div>
   </div>
</template>

<script>
export default {
  props: ['value'],
  methods: {
    close () {
      this.$emit('input',false)
    }
  }
}
</script>

這是一個模態框的基本雛形,可以在父元件通過 v-model 來進行 model 框和父元件之間的顯示互動。

通過子元件看出通過props接收了value值,當點選關閉的時候還是通過$emit事件觸發input事件,然後通過傳入 false 引數。

父元件隱式 v-on:input=“something = arguments[0]” 進行了監聽,一但 Input 事件觸發,父元件就會執行監聽回撥,從而做到了雙向繫結。

<!--checkbox 和 radio 原理-->
 <input type="checkbox" :checked="status" @change="status = $event.target.checked" />

<input type="radio" :checked="status" @change="status = $event.target.checked" />

通過繫結 checked 屬性,同樣的監聽的是 change 事件,無論是 checkbox 還是 radio 在操作的時候都會隱式自動觸發一個 change 事件,跟 Input 通過 value 值,Input 觸發事件原理繫結是一樣的。

定製元件 v-model

定製元件,我們就可以重寫v-model裡的Props 和 event,預設情況下,一個元件的 v-model 會使用 value 屬性和 input 事件,往往有些時候,value 值被佔用了,或者表單的和自定議v-model的$emit(‘input’)事件發生衝突,為了避免這種衝突,可以定製元件 v-model,衝突示例。

子元件

<template>
   <div v-show="value">
      <div>
         <p>這是一個Model框</p>
        <input type="text" v-model="value">
        {{value}}
         <button @click="close">關閉model</button>
      </div>
   </div>
</template>

<script>
export default {
  props: ['value'],
  methods: {
    close () {
      this.$emit('input',false)
    }
  }
}
</script>

父元件

<template>
  <div class="hello">
    <button @click="show=true">開啟model</button>
    <demo v-model="show"></demo>
  </div>
</template>

<script>
  import Demo from './Demo.vue'
  export default {
    name: 'hello',
    components: {
      Demo
    },
    data () {
      return {
        show: false
      }
    }
  }
</script>

上面例子可以發現,在子元件中input中v-model和model顯示的操作資料共同佔用的 props 中的(value),同樣兩者也共同佔用了 emit(‘input’) 觸發事件,Input 輸入框的事件是自動出發,而 model 顯示消失是手動觸發。

初始化的時候,Input 輸入框的值的會被 value 傳入的 false 值給自動加上,當改變 Input 輸入框的時候,因為衝突而導致報錯。

定製 v-model, 通過 model 選項改變 props 和 event 的值,從而解除兩者的衝突。

props代替掉原本 value 的值,可以自定義值
event代表掉原本 input 的觸發事件,可以自定義觸發事件
子元件

<template>
   <div v-show="show">
      <div>
         <p>這是一個Model框</p>
        <input type="text" v-model="value">
        {{value}}
         <button @click="closeModel">關閉model</button>
      </div>
   </div>
</template>

<script>
export default {
  model: {
    prop: 'show',
    event: 'close'
  },
  props: ['show'],
  data () {
     return {
       value: 10
     }
  },
  methods: {
    closeModel () {
      this.$emit('close',false)
    }
  }
}
</script>

父元件

<template>
  <div class="hello">
    <button @click="show=true">開啟model</button>
    <demo v-model="show" ></demo>
  </div>
</template>

<script>
  import Demo from './Demo.vue'
  export default {
    name: 'hello',
    components: {
      Demo
    },
    data () {
      return {
        show: false
      }
    }
  }
</script>

通過 model 選項的改變,把 props 從原本的value換成了show,input觸發的事件換成了close,從而兩者都不相互依賴,解決了衝突的問題。

有些時候通過父元件中的子元件模板中想傳遞 value 值,也會導致同樣的衝突。

在不用定製元件的情況下,以下的寫法,也會同樣導致衝突,導致同用一個 value。

<demo v-model="show" value="some value"></demo>
props:['value']

王者回歸 .sync

在一些情況下,我們可能會需要對一個 prop 進行『雙向繫結』。事實上,這正是 Vue 1.x 中的 .sync 修飾符所提供的功能。當一個子元件改變了一個 prop 的值時,這個變化也會同步到父元件中所繫結的值。這很方便,但也會導致問題,因為它破壞了『單向資料流』的假設。由於子元件改變 prop 的程式碼和普通的狀態改動程式碼毫無區別,當光看子元件的程式碼時,你完全不知道它何時悄悄地改變了父元件的狀態。這在 debug 複雜結構的應用時會帶來很高的維護成本。

在2.0釋出一段之後,無論在業務元件還是在功能元件庫上面的,大量的子元件改變父子元件的資料和元件庫中可能達到大功率的複用,但是在2.3中迴歸,重新引入了 .sync 修飾符,這次它只是作為一個編譯時的語法糖存在。它會被擴充套件為一個自動更新父元件屬性的 v-on 偵聽器。

之前的例子中,v-model 畢竟不是給元件與元件之間通訊而設計的雙向繫結,無論從語意上和程式碼寫法上都沒有 .sync 直觀和方便。

無論從 v-model 還是 .sync 修飾符來看,都離不開 $emit v-on 語法糖的封裝,主要目的還是為了保證資料的正確單向流動與顯示流動。

<demo :foo.sync="something"></demo>

語法糖的擴充套件:

<demo :foo="something" @update:foo="val => something = val"></demo>

foo 則是 demo 子元件需要從父元件 props 接收的資料
通過事件顯示監聽 update:foo (foo則是 props 顯示監聽的資料),通過箭頭函式執行回撥,把引數傳給 something,則就形成了一種雙向繫結的迴圈鏈條
當子元件需要更新 foo 的值時,它需要顯式地觸發一個更新事件:

this.$emit('update:foo', newValue)

同時父元件@update:foo也是依賴於子元件的顯示觸發,這樣就可以很輕鬆的捕捉到了資料的正確的流動。

第一個引數則是 update 是顯示更新的事件,跟在後面的:foo則是需要改變對應的props值。

第二個引數傳入的是你希望父元件foo資料裡將要變化的值,以用於父元件接收update時更新資料。

子元件

<template>
   <div v-show="show">
      <p>這是一個Model框</p>
      <button @click="closeModel">關閉model</button>
   </div>
</template>
<script>
export default {
  props: ['show'],
  methods: {
    closeModel () {
      this.$emit('update:show',false)
    }
  }
}
</script>

父元件

<template>
  <div class="hello">
    <button @click="show=true">開啟model</button>
    <demo :show.sync="show" ></demo>
  </div>
</template>

<script>
  import Demo from './Demo.vue'
  export default {
    name: 'hello',
    components: {
      Demo
    },
    data () {
      return {
        show: false
      }
    }
  }
</script>

上面的 case 同樣也解決了 model 顯示互動操作,從程式碼的語意上看上去讓開發者一目瞭然,同樣也做了 v-model 做不了的事,基於 props 的原子化,對傳入的 props 進行多個數據雙向繫結.sync 也能輕鬆做到。

父元件

<template>
  <div class="hello">
    <button @click="show=true">開啟model</button>
    <demo :show.sync="show" :msg.sync="msg"></demo>
  </div>
</template>

<script>
  import Demo from './Demo.vue'
  export default {
    name: 'hello',
    components: {
      Demo
    },
    data () {
      return {
        show: false,
        msg: '這是一個model'
      }
    }
  }
</script>

子元件

<template>
   <div v-show="show">
      <p>{{msg}}</p>
      <button @click="closeModel">關閉model</button>
     <button @click="$emit('update:msg','改變了model文案')">改變文案</button>
   </div>
</template>
<script>
export default {
  props: ['show', 'msg'],
  methods: {
    closeModel () {
      this.$emit('update:show',false)
    }
  }
}
</script>

warn

子元件改變父元件的資料時,update 冒號後面的引數和父元件傳遞進來的值是同步的,想改變那個,則冒號後面的值對應的那個,兩者是一一對應的,同時也是必填的。

同樣還可以在元件 template 裡點選執行 click 後不但可以支援回撥函式,還可以寫入表示式,只是一種直觀的表現還是推薦這種寫法的。

.sync 修飾符給我們開發中帶來了很大的方便,同時在2.0的初期的元件庫中大量的 v-model 給開發者用起來還是很彆扭,在.sync 迴歸後同時也會慢慢向.sync 進行一個版本的遷移。
轉載GitChat