v-model双向绑定
1、普通版实现
- 通过props接收
value
,作为input输入框的property (dom属性:加了.prop修饰符) - 绑定输入框input事件,触发
input
事件,通知父组件更新数据
// input.vue
<template>
<input type="text" :value.prop="value" @input="notify" />
</template>
<script>
export default {
props: { value: String },// 必须用 "value" 这个prop
methods: {
notify(e) {
// 输入框的值在e.target.value
this.$emit("input", e.target.value);// 必须触发 "input" 事件
},
};
</script>
// ---------------------------
// 假如不想使用 "value" 这个prop和 "input" 这个事件名怎么办?
// 可以指定model
<template>
<input type="text" :value.prop="book" @input="notify" />
</template>
<script>
export default {
model: { prop: "book", event: "change" },// 指定prop为"book",指定事件为"change"
props: { book: String },// 接收book
methods: {
notify(e) {
this.$emit("change", e.target.value); // 触发"change"事件
},
},
};
</script>
// ---------------------------
// father.vue
<template>
<the-input v-model="book"></the-input> // 使用v-model,绑定book
</template>
<script>
import theInput from "./input.vue";
export default {
components: { theInput },
data() {
return {
book: "",
};
},
};
</script>
可以发现prop必须是"value",触发的事件必须是"input",这是因为v-model默认绑定这俩键名
通过设定model
可以指定新的prop与事件名称
子组件编写完成后,在父组件引入,这时候就可以使用v-model了
2、通过watch实现
- 用上面的方法使用v-model是比较简洁清爽的,但是子组件只能被动的接收prop中的value,没有自身的状态
- 通过watch就能让组件拥有自己的状态
- 先讲实现思路
- 子组件通过props接收value,赋值给自身内部的
internalValue
, - 监听
value
变化实时更新internalValue ; - 另外绑定
input
事件,通知父组件更新value,
// input.vue
<template>
<input type="text" :value.prop="internalValue" @input="notify" />
</template>
<script>
export default {
props: { value: String },
data() {
return {
internalValue: this.value, // 内部value
};
},
watch: {
value(val) {
this.internalValue = val; // 实时更新数据
},
},
methods: {
notify(e) {
this.$emit("input", e.target.value); // 更新父组件数据
},
},
};
</script>
// ---------------------------
// father.vue
<template>
<the-input v-model="book"></the-input> // 使用v-model,绑定book
</template>
<script>
import theInput from "./input.vue";
export default {
components: { theInput },
data() {
return {
book: "",
};
},
};
</script>
如何实现一个能够自动过滤首尾空格的v-model?
只需要在watch里边赋值的时候对传过来的值进行修剪操作就好了
watch: {
value(val) {
this.internalValue = val.trim(); // 实时更新数据,并且裁剪字符串两边的空格
},
},
- 每次输入框输入的时候,会触发input事件,这时候更新父组件的value
- 子组件watch到value变化,于是尝试更新internalValue
- 更新internalValue的同时,顺便修剪了一下首尾的空格
- 这时候输入框的value就是修剪之后的值了
.sync修饰符双向绑定
// input.vue
<template>
<input type="text" :value.prop="book" @input="notify" />
</template>
<script>
export default {
props: { book: String },
methods: {
notify(e) {
this.$emit("update:book", e.target.value); // 更新父组件数据
},
},
};
</script>
// ---------------------------
// father.vue
<template>
<the-input :book.sync="book"></the-input>
</template>
<script>
import theInput from "./input.vue";
export default {
components: { theInput },
data() {
return {
book: "",
};
},
};
</script>
sync
修饰符和v-model
很像,都是接收一个prop、触发一个update事件- 区别在于v-model必须接收特定的prop(value)和触发特定的事件(input)
- 而sync修饰符可以接收任意prop,在更新的时候触发对应的
update
事件就好了 - 所以可以把sync修饰符理解为灵活版本的v-model
render函数形式
<script>
export default {
render(h) {
return h("input", {
domProps: { value: this.value },
on: { input: this.notify },
});
},
props: { value: String },
methods: {
notify(e) {
this.$emit("input", e.target.value); // 更新父组件数据
},
},
};
</script>
template模板最终会编译成render函数,所以直接用render写法是一种更高效的方式
相比较而言render写起来会更难阅读一些,具体用法参考vue官方文档