點燈坊

失くすものさえない今が強くなるチャンスよ

使用 .sync 達成 Prop 之 Two-way Binding

Sam Xiao's Avatar 2020-09-25

Vue 允許我們對 HTML Tag 使用 v-model 達成 Two-way Binding,我們除了也可使用 v-model 對 Prop 達成 Two-way Binding 外,也可使用 .sync

Version

macOS Catalina 10.15.6
Vue 2.6.11

Custom Event

sync000

4outerCount model,而 inner +MyCounter 內的 button。

outerCount 會傳進 MyCounter,且當 inner + 遞增時也會 two-way binding 更新 outerCount

App.vue

<template>
  <div>
    {{ outerCount }}
    <MyCounter :start-count="outerCount" @update:startCount="onUpdateStartCount"></MyCounter>
  </div>
</template>

<script>
import MyCounter from '@/components/MyCounter.vue'

let onUpdateStartCount = function(v) {
  this.outerCount = v
}

export default {
  data: () => ({
    outerCount: 3
  }),
  components: {
    MyCounter
  },
  methods: {
    onUpdateStartCount
  }
}
</script>

16 行

data: () => ({
  outerCount: 3
}),

定義 outerCount model,且初始值為 3

第 2 行

<div>
  {{ outerCount }}
  <MyCounter :start-count="outerCount" @update:startCount="onUpdateStartCount"></MyCounter>
</div>

outerCount 除了傳入 <MyCounter>start-count prop 外,也在外層顯示,因此我們希望 outerCount能 two-way binding。

MyCounter.vue

<template>
  <button @click="onClick">inner +</button>
</template>

<script>
let onClick = function() {
  this.$emit('update:startCount', this.startCount +1)
}

export default {
  props: [
    'startCount'
  ],
  methods: {
    onClick
  }
}
</script>

11 行

props: [
  'startCount'
],

自訂 startCount prop。

第 6 行

let onClick = function() {
  this.$emit('update:startCount', this.startCount +1)
}

自行發出 update:prop名稱 event。

.sync

App.vue

<template>
  <div>
    {{ outerCount }}
    <MyCounter :start-count.sync="outerCount"></MyCounter>
  </div>
</template>

<script>
import MyCounter from '@/components/MyCounter.vue'

export default {
  data: () => ({
    outerCount: 3
  }),
  components: {
    MyCounter
  }
}
</script>

第 4 行

<MyCounter :start-count.sync="outerCount"></MyCounter>

雖然可使用 prop 加 event 達成 two-way binding,但也可使用 .sync modifier,一看就知道 start-count prop 為 two-way binding,可讀性更高。

MyCounter.vue

<template>
  <button @click="onClick">inner +</button>
</template>

<script>
let onClick = function() {
  this.$emit('update:startCount', this.startCount +1)
}

export default {
  props: [
    'startCount'
  ],
  methods: {
    onClick
  }
}
</script>

第 6 行

let onClick = function() {
  this.$emit('update:startCount', this.startCount +1)
}

若要使用 .sync,Vue 規定一定要發出 update:prop名稱 event。

可發現 .sync modifier 為 syntatic sugar,也可自行接收 custom event 達成 two-way binding

v-model vs. .sync

v-model.sync 都能達成 prop 的 two-way binding,但有以下差異:

  • 一個 component 只能有一個 v-model,但能有多個 .sync
  • v-model 必須使用 value prop 與 input event;.sync 可自訂 prop 名稱,event 則為 update:prop名稱
  • 若只有一個 prop 需 two-way binding,建議使用 v-model;若有多個 prop 需 two-way binding,則可第一個使用 v-model,其他使用 .sync,或者全部使用 .sync

Conclusion

  • Component 傳遞資料的關鍵在於 prop 與 event,.sync 的 two-way binding 雖然看似神奇,但骨子裡還是離不開 prop 與 event,prop 名稱可自訂,但 event 則為 update:prop名稱
  • v-model.sync 非常類似,目的都是使 prop 能 two-way binding,也因為太類似,Vue 3 已經廢除 .sync,但可同時使用多 v-model,event 則為 update:model名稱

Reference

Vue, .sync Modifier