什么是双向绑定
所谓的双向绑定是指数据发生变化时,视图会同步发生变化,而当视图发生变化时,数据也会同步变化。
Vue中怎么实现双向绑定
在Vue中,我们通过v-model来创建双向绑定。
我们继续用todolist和todoitme组件来示例双向绑定
- 在App.vue的data中增加一个message。
data(){ return{ message:"hello world", list: [ { title: "新课程1", del: false }, { title: "新课程2", del: true }, { title: "新课程3", del: false } ] }; },
- 在App.vue的template中通过模板语法增加对应的message展示。
{{message}}
- 创建一个表单控件,通过v-model实现message的双向绑定。
<input type="text" v-model="message"/>
全部代码如下
<template> <div id="app"> <input type="text" v-model="message"/> {{message}} <todolist> <todoitem v-on:delete="handleDelete" v-for="item in list" :key="item.title" data-wen="wen" :title="item.title" :del="item.del"> <template v-slot:pretext="{val}"> <label>前置文字{{val}}</label> </template> </todoitem> </todolist> </div> </template> <script> import todolist from './components/todo-list.vue' import todoitem from './components/todo-item.vue' export default { name: 'App', components: { todolist, todoitem }, data(){ return{ message:"hello world", list: [ { title: "新课程1", del: false }, { title: "新课程2", del: true }, { title: "新课程3", del: false } ] }; }, methods: { handleDelete(vtitle){ console.log("删除工程!",vtitle) } } } </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>
效果如下:在表单中修改message,不论是脚本中的message变量还是动态绑定的message模板显示都会同步变化。
Vue双向绑定的本质
按照Vue官网介绍,Vue的双向绑定是一种语法糖。本质上是
负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。
它其实还是一种单向的数据流操作。
它的还原写法即本质是value属性和input事件的组合,代码如下:(单单相对于input的text控件来说):
<input type="text" :value="message" @input="handleChange"/>
handleChange(e){ this.message = e.target.value; },
完整代码如下:
<template> <div id="app"> <input type="text" v-model="message"/> <input type="text" :value="message" @input="handleChange"/> {{message}} <todolist> <todoitem v-on:delete="handleDelete" v-for="item in list" :key="item.title" data-wen="wen" :title="item.title" :del="item.del"> <template v-slot:pretext="{val}"> <label>前置文字{{val}}</label> </template> </todoitem> </todolist> </div> </template> <script> import todolist from './components/todo-list.vue' import todoitem from './components/todo-item.vue' export default { name: 'App', components: { todolist, todoitem }, data(){ return{ message:"hello world", list: [ { title: "新课程1", del: false }, { title: "新课程2", del: true }, { title: "新课程3", del: false } ] }; }, methods: { handleChange(e){ this.message = e.target.value; }, handleDelete(vtitle){ console.log("删除工程!",vtitle) } } } </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>
为什么说上述的原始写法只针对于input 的text呢?
vue官网解释如下:
v-model 在内部为不同的输入元素使用不同的属性并抛出不同的事件:
- text 和 textarea 元素使用 value 属性和 input 事件;
- checkbox 和 radio 使用 checked 属性和 change 事件;
- select 字段将 value 作为 prop 并将 change 作为事件。
- .....
如何为组件添加自定义事件绑定
原生的双向绑定可以大大简化我们的编码,那么如何为我们自己封装的组件添加自定义的双向绑定呢?
为了实现这一点,我们需要为我们的组件增加一个model对象,并在model对象中告知vue框架本次双向绑定对应的属性和事件。
如下示例则是告知vue底层该组件监听checked属性的change事件。
model: { prop: 'checked', event: 'change' },
我们尝试修改todoitem的template和data,增加选择控件和选择后的文本。
- 第一步为组件增加model对象
model: { prop: 'itemCheck', event: 'change' },
- 第二步在组件的props中注册itemCheck属性
props: { title: String, itemCheck:{ type: Boolean, default: false }, del: { type: Boolean, default: false } },
- 第三步修改template,增加checkbox和选中的状态描述。需要注意的是:需要使用完全实现v-model的本质上的属性绑定和事件编码。
<template> <li> <input type="checkbox" :name="vrandom" :key="vrandom" :checked="itemCheck" @change="$emit('change', $event.target.checked)" /> <slot name="pretext" :val="vrandom"></slot> <span class="redsapn" v-if="!del">{{title}}</span> <span v-else style="text-decoration:line-through">{{title}}</span> <button v-show="!del" @click="handleClick">删除</button> <slot name="suftext">默认尾部</slot> </li> </template>
- 最后我们在APP.vue中的template中使用todoitem时就可以通过v-model来实现todoitem组件的双向绑定了。
<template> <div id="app"> <input type="text" v-model="message"/> <input type="text" :value="message" @input="handleChange"/> {{message}} <todolist> <todoitem v-on:delete="handleDelete" v-model="Checkedmsg" v-for="item in list" :key="item.title" data-wen="wen" :title="item.title" :del="item.del"> <template v-slot:pretext="{val}"> <label>前置文字{{val}}</label> </template> </todoitem> </todolist> </div> </template>
完整代码如下:
//App.vue <template> <div id="app"> <input type="text" v-model="message"/> <input type="text" :value="message" @input="handleChange"/> {{message}} <todolist> <todoitem v-on:delete="handleDelete" v-model="Checkedmsg" v-for="item in list" :key="item.title" data-wen="wen" :title="item.title" :del="item.del"> <template v-slot:pretext="{val}"> <label>前置文字{{val}}</label> </template> </todoitem> </todolist> </div> </template> <script> import todolist from './components/todo-list.vue' import todoitem from './components/todo-item.vue' export default { name: 'App', components: { todolist, todoitem }, data(){ return{ message:"hello world", Checkedmsg:false, list: [ { title: "新课程1", del: false }, { title: "新课程2", del: true }, { title: "新课程3", del: false } ] }; }, methods: { handleChange(e){ this.message = e.target.value; }, handleDelete(vtitle){ console.log("删除工程!",vtitle) } } } </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>
//todo-item.vue <template> <li> <input type="checkbox" :name="vrandom" :key="vrandom" :checked="itemCheck" @change="$emit('change', $event.target.checked)" /> <slot name="pretext" :val="vrandom"></slot> <span class="redsapn" v-if="!del">{{title}}</span> <span v-else style="text-decoration:line-through">{{title}}</span> <button v-show="!del" @click="handleClick">删除</button> <slot name="suftext">默认尾部</slot> </li> </template> <script> export default { model: { prop: 'itemCheck', event: 'change' }, props: { title: String, itemCheck:Boolean, del: { type: Boolean, default: false } }, data: function() { return { vrandom:Math.random() }; }, methods: { handleClick(){ console.log("点击删除按钮!"); this.$emit('delete',this.title); } } } </script> <style scoped> .redsapn{color: red} </style>
来源:https://www.cnblogs.com/wenpeng/p/12288313.html