Vue2 中使用 v-model 实现自定义数据双向绑定。
默认情况
默认情况下,v-model 会把父组件的数据(比如 v-model="count" 中的 count)绑定到子组件的 value 属性上;
子组件通过 $emit 触发 input 事件时,Vue 会将事件的参数传递回父组件,从而更新父组件的数据。
父组件:
<!-- 父组件 -->
<template>
<div>
<Children v-model="count"></Children>
<p>----------</p>
<p>
父: {{ count }}
<button @click="handleAdd">父按钮</button>
</p>
</div>
</template>
<script>
import Children from './Children.vue'
export default {
components: { Children },
data() {
return {
count: 1
}
},
mounted() {
handleAdd() {
this.count++;
}
}
}
</script>
子组件:
<!-- 子组件 -->
<template>
<div>
子:
<input :value="value" @input="handleInput">
<button @click="handleAdd">子按钮</button>
</div>
</template>
<script>
export default {
props: {
value: {
type: Number,
default: 2
},
},
methods: {
handleInput(e) {
console.log(typeof e.target.value) // string
this.$emit('input', Number(e.target.value));
},
handleAdd() {
this.value++; // 报错
}
},
watch: {
value: {
handler(newVal, oldVal) {
console.log("newVal:", newVal)
console.log("oldVal:", oldVal)
},
immediate: true
}
}
}
</script>
此时在 input 框中输入数字、或点击父组件的按钮,都会实现父子组件的双向数据绑定.
注意点:
-
父组件中 count 的默认值为 1,子组件中 count 的默认值为 2,实际显示的默认值是 1。
-
页面初始化时 watch 中打印的内容为:
newVal: 1,oldValu: undefined -
e.target.value 的数据类型是 string
父组件中改变双向绑定的数据很简单,例如在上例中,点击父按钮可以轻松实现,但是点击子按钮就会报错:
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,因为每当父组件重新渲染时,该属性的值都会被覆盖。应根据该属性的值使用 data 或计算属性 computed。正在被修改的属性:"value"
下面给出正确示例,父组件代码不变,只修改子组件代码:
方式一:使用 data
<!-- 子组件 -->
<template>
<div>
子:
<input :value="value" @input="handleInput">
<button @click="handleAdd">子按钮</button>
</div>
</template>
<script>
export default {
props: {
value: {
type: Number,
default: 2
},
},
data() {
localValue: this.value
},
methods: {
handleInput(e) {
console.log(typeof e.target.value) // string
this.$emit('input', Number( e.target.value));
},
handleAdd() {
this.localValue++;
this.$emit('input', this.localValue);
}
}
// watch 内容省略
}
</script>
方式二:使用 computed
<!-- 子组件 -->
<template>
<div>
子:
<input :value="value" @input="handleInput">
<button @click="handleAdd">子按钮</button>
</div>
</template>
<script>
export default {
props: {
value: {
type: Number,
default: 2
},
},
computed: {
localValue: {
get() {
return this.value
},
set(newVal) {
this.$emit('input', newVal)
}
}
},
methods: {
handleInput(e) {
console.log(typeof e.target.value) // string
this.$emit('input', Number( e.target.value));
},
handleAdd() {
this.localValue++;
}
}
// watch 内容省略
}
</script>
两种方式的本质都是通过 $emit 触发 input 事件,将更新后的值传递给父组件。
如果子组件中不是 input,而是 select 呢?其实本质是一样的,都是通过 $emit 触发 input 事件,将更新后的值传递给父组件。
下面给出示例,父组件代码不变,只修改子组件代码:
<!-- 子组件 -->
<template>
<div>
子:
<el-select v-model="localValue">
<el-option v-for="item in 10"
:key="item"
:label="`选项`${item}"
:value="item"
>
</el-option>
</el-select>
<button @click="handleAdd">子按钮</button>
</div>
</template>
<script>
export default {
props: {
value: {
type: Number,
default: 2
},
},
computed: {
localValue: {
get() {
return this.value
},
set(newVal) {
this.$emit('input', newVal)
}
}
},
data() {
// localValue: this.value
/*
data 中也可以,但是需要手动捕获 localValue 的变化事件,
在变化的时候调用 this.$emit('input', this.localValue) 方法,
不如 computed 方便。
*/
},
methods: {
handleAdd() {
this.localValue++;
}
}
// watch 内容省略
}
</script>
无论换成什么,只要在变化的时候使用 this.$emit 触发默认的 input 事件就可以。
自定义属性和事件
在子组件中,你可以通过 model 选项来指定一个不同的 prop 名称用于接收值,以及一个不同的 event 名称用于触发更新。
父组件代码还是不变:
<!-- 父组件 -->
<template>
<div>
<Children v-model="count"></Children>
<p>----------</p>
<p>
父: {{ count }}
<button @click="handleAdd">父按钮</button>
</p>
</div>
</template>
<script>
import Children from './Children.vue'
export default {
components: { Children },
data() {
return {
count: 1
}
},
mounted() {
handleAdd() {
this.count++;
}
}
}
</script>
子组件把属性定义为 aaProp,事件定义为 bbEven:
<!-- 子组件 -->
<template>
<div>
子:
<input :value="aaProp" @input="handleInput">
</div>
</template>
<script>
export default {
model: {
prop: "aaProp",
event: "bbEven"
}
props: {
aaProp: {
type: Number,
default: 2
},
},
methods: {
handleInput(e) {
this.$emit('bbEven', Number(e.target.value));
},
}
}
</script>