1. v-model
實現自定義元件雙向繫結
v-model
其實是個語法糖,如果沒按照相應的規範定義元件,直接寫v-model
是不會生效的。再說一遍,類似於v-on:click
可以簡寫成@click
,v-model
是兩個表示式合在一起的簡寫。記住這個,下面具體說明。
1.1 input
雙向繫結
子元件MyInput.vue
:
<template>
<div>輸入
<input :value="value" @input="input"/>
</div>
</template>
<script>
export default {
name: "MyInput",
props: {
value: {type: [String, Number]}
},
methods: {
input(e) {
this.$emit('input', e.target.value)
}
}
}
</script>
父元件App.vue
中使用:
<template>
<div id="app">
<my-input v-model="haha" />
<div>{{haha}}</div>
</div>
</template>
<script>
import MyInput from '@/components/MyInput'
export default {
name: 'App',
components: { MyInput },
data() {
return {
haha: '66666',
}
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
<my-input v-model="haha" />
是一個語法糖(簡寫),它相當於:
<my-input
:value="haha"
@input="onInput"
/>
onInput(e) {
this.haha = e
}
兩者效果一樣,
v-model
減少了很多程式碼,不用你手動把傳過來的值賦值給haha
了,少定義了一個方法。作用還是挺明顯的。
MyInput
元件中的value
不可以叫別的名字,$emit
中丟擲的方法也只能叫input
,否則都不可以用v-model
語法糖,用了也沒啥效果。
另外,獲取 input 輸入值的的方式是:$event.target.value
,寫成方法的話,$event
實參省略了。
1.2. checkbox
雙向繫結
到了checkbox
這,又有點不一樣,首先,它繫結的值不叫value
,觸發事件也不叫input
,具體看程式碼。
子元件MyCheckBox
程式碼:
<template>
<div>
<input type="checkbox" :checked="checked" @change="change"/>
</div>
</template>
<script>
export default {
name: "MyCheckBox",
model: {
prop: 'checked',
event: 'zhuangbi'
},
props: {
checked: Boolean
},
methods: {
change(e) {
this.$emit('zhuangbi', e.target.checked)
}
}
}
</script>
父元件App.vue
中使用:
<template>
<div id="app">
<my-check-box v-model="changeV"></my-check-box>
<div>{{changeV}}</div>
</div>
</template>
<script>
import MyCheckBox from '@/components/MyCheckBox'
export default {
name: 'App',
components: { MyCheckBox },
data() {
return {
changeV: true,
}
}
}
</script>
除了繫結的
value
變成了checked
,@input
變成了@change
,傳到父元件的引數是e.target.checked
注意: 絕大多數例子中,
$emit
中丟擲的事件都是change
,我這裡寫的是zhuangbi
,其實只要和model
模組中event
屬性值一樣即可。
此外,外面的props
也不可少。
2. .sync
方式實現雙向繫結
如果需要對一個prop
進行雙向繫結,可以使用.sync
語法糖。舉一個使用場景的例子:別人封裝好的 CheckBox 元件,需要做一些樣式修改或者功能組合再使用,這就需要對 v-model 的值再來一次雙向繫結。拿上面的 MyCheckBox 來說,<my-check-box v-model="checked"/>
,給這個checked
傳值可以用 props
,但想把checked
的值傳給父元件並賦值給props
的值,就有點麻煩,需要定義一個方法,使用$emit
,父元件監聽事件並作賦值操作。現在用.sync
可以一句搞定。
子元件DiyCheckBox
程式碼:
<template>
<div>
<my-check-box v-model="diyCheck" @change="dChange"/>
</div>
</template>
<script>
import MyCheckBox from './MyCheckBox'
export default {
name: "DiyCheckBox",
components: {MyCheckBox},
props: {
diyCheck: Boolean,
test: String
},
methods: {
dChange(e) {
this.$emit('update:diyCheck', e)
}
}
}
</script>
父元件App.vue
中使用:
<template>
<div id="app">
<diy-check-box :diyCheck.sync="dCheck" />
<div>{{dCheck}}</div>
</div>
</template>
<script>
import DiyCheckBox from '@/components/DiyCheckBox'
export default {
name: 'App',
components: { DiyCheckBox },
data() {
return {
dCheck: true,
}
}
}
</script>
:diyCheck.sync="dCheck"
這句程式碼相當於:
:diyCheck="dCheck"
@update:diyCheck="dCheck = $event"
語法糖作用很明顯,大大簡化了程式碼。
上面程式碼可以實現想要的功能,只是控制檯會有一個警告:
[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: "diyCheck"
found in
---> <DiyCheckBox> at src/components/DiyCheckBox.vue
<App>
<Root>
避免直接改變props
,使用data or computed
代替。所以優化一下,換個寫法:
<template>
<div>
<my-check-box v-model="comDiyCheck"/>
</div>
</template>
<script>
import MyCheckBox from './MyCheckBox'
export default {
name: "DiyCheckBox",
components: {MyCheckBox},
props: {
diyCheck: Boolean,
test: String
},
computed: {
comDiyCheck: {
get() {
return this.diyCheck
},
set(e) {
this.$emit('update:diyCheck', e)
}
}
}
}
</script>
使用計算屬性後,@change
事件都可以不要了,get()
就是獲取props
傳值,set(e)
是MyCheckBox
的v-model
值改變了會觸發的方法,這裡面做法是直接把改變後的值通過$emit
方式發出去。父元件中仍然通過.sync
繫結,程式碼沒有變化。
.sync
可不光能用來做checkbox
的雙向繫結,涉及到props
雙向繫結的場景都可以用sync
實現。
.sync
傳整個物件
如果有許多props
屬性需要做雙向繫結操作,標籤寫起來就很長,像這樣:
<coverage-charge
v-for="(item, index) in chargingPiles"
:key="index + 'index'"
:code.sync="item.code"
:address.sync="item.address"
:addressType.sync="item.addressType"
:kind.sync="item.kind"
:yearLimitType.sync="item.yearLimitType"
>
</coverage-charge>
官方文件說可以簡寫成這樣:
<text-document v-bind.sync="doc"></text-document>
<!--對應我們的例子就是:-->
<coverage-charge
v-for="(item, index) in chargingPiles"
:key="index + 'index'"
v-bind.sync='item'
>
</coverage-charge>
官方還說:
這樣會把
doc
物件中的每一個property
(如title
) 都作為一個獨立的prop
傳進去,然後各自新增用於更新的v-on
監聽器。
按照這個說法,item
的 5 個屬性,都會通過 prop
傳到子元件,我再在子元件中新增不同的computed
屬性即可:
<script>
export default {
name: 'CoverageCharge',
props: {},
computed: {
code: {
get() {
return this.code
},
set(val) {
this.$emit('update:code', val)
}
},
address: {
get() {
return this.address
},
set(val) {
this.$emit('update:address', val)
}
}
... // 其他屬性值
}
}
</script>
真這樣寫,會發現,this.code
、this.address
這些都是undefined
,這裡需要在子元件的props
中再定義一遍屬性名才行,可以省略各屬性的type
和default
值:
props: [ 'code', 'address', 'addressType', 'kind', 'yearLimitType' ]
這樣就可以了,.sync
功能強大,用的地方也挺多的。
props
裡面東西太多的話,也可以統一定義成一個物件,然後父元件通過v-bind
或者v-model
傳進來:
2.1.1 v-bind
方式
<!--子元件-->
props: {
zb: {
type: Object,
default: () => {}
}
},
computed: {
code: {
get() {
return this.zb.code
},
set(val) {
this.$emit('update:code', val)
}
},
address: {
get() {
return this.zb.address
},
set(val) {
this.$emit('update:address', val)
}
}
}
<!--父元件-->
<coverage-charge
v-for="(item, index) in chargingPiles"
:key="index + 'index'"
v-bind.sync='item'
:zb='item'
>
</coverage-charge>
2.2.2 v-model
方式
<!--子元件-->
export default {
model: {
prop: 'zb',
event: 'update'
},
props: {
zb: {
type: Object,
default: () => {}
}
},
computed: {
code: {
get() {
return this.zb.code
},
set(val) {
this.$emit('update:code', val)
}
},
address: {
get() {
return this.zb.address
},
set(val) {
this.$emit('update:address', val)
}
}
}
}
<!--父元件-->
<coverage-charge
v-for="(item, index) in chargingPiles"
:key="index + 'index'"
v-bind.sync='item'
v-model='item'
>
</coverage-charge>
注意
model
中event
值是update
。
3. v-model
和.sync
比較
上面第 2 節第一個例子目的就是把v-model
再包一層,照著第 1 節,用v-model
的方法也能做到。這裡只寫主要點,不貼原始碼了:
- 定義
model
模組:model: {prop: 'checked', event: 'zhuangbi' }
props
中定義checked
屬性computed
中定義MyChecked: { get(){ return this.checked},set(val) { this.$emit('zhuangbi', val) } }
DiyCheckBox
元件中使用:<my-check-box v-model="MyChecked"/>
- 父元件中使用:
<diy-check-box v-model=suibian" />
,suibian
是父元件data
中定義的變數
可見,v-model
適用於雙向繫結單個props
(一個標籤中不能有多個v-model
),.sync
適用於雙向繫結一到多個props
(一個標籤中允許使用多個:xxx.sync=yyy
)。