vue学习笔记(八)组件校验&通信
前言
在上一章博客的内容中vue学习笔记(七)组件我们初步的认识了组件,并学会了如何定义局部组件和全局组件,上一篇内容仅仅只是对组件一个简单的入门,并没有深入的了解组件当中的其它机制,本篇博客将会带大家深入了解组件的其它知识,组件的校验,组件的通信等等。
本章目标
- 学会组件简单的校验
- 学会父组件向子组件传递数据
- 学会子组件向父组件传递数据
父组件向子组件传递数据
父组件向子组件传递数据实现的方式特别简单,只用使用props进行数据传递就可以了。
语法:props[‘属性1’,‘属性2’,…]
我找了一张图给大家参考一下
在 Vue.js 中,父子组件的关系可以总结为 props down, events up 。父组件通过 props 向下传递数据给子组件,子组件通过 events 给父组件发送消息
(1)简单的父组件向子组件传递信息
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>父组件向子组件传递信息</title> </head> <body> <div id="app"> <my-content :title='title' :content='content'></my-content> </div> <script src="../js/vue.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> let vm=new Vue({ el:'#app', data:{ title:'标题', content:'内容' }, methods:{ }, computed:{ }, components:{ 'myContent':{ props:['title','content'], template:`<div> <h1>{{title}}</h1> <p>{{content}}</p> </div>` } } }) </script> </body> </html>
结果:显示标题和内容,最简单的父组件向子组件传递信息我们就实现了,但是里面还有其它的小知识点我们没有讲解到,而且后期开发都是使用vue-cli来实现父组件向子组件传递数据的,所以这个知识点下一篇博客会讲解到,不会搭建vue-cli项目的朋友可以参考这篇博客使用webstorm搭建vue-cli项目后续的许多文章都会使用vue-cli中的组件进行讲解,而不是通过简单的引入vue.js文件了,所以强烈推荐大家一定要学会搭建vue-cli项目。
总结:父组件向子组件传递数据使用props
(2)props传递整个对象
假设父组件中的对象含有多个属性,我们每一个属性都需要进行传递,那么是不是需要绑定每一个属性呢?
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>props传递多个属性</title> </head> <body> <div id="app"> <my-content :title="attr.title" :content1="attr.content1" :content2="attr.content2"></my-content> </div> <script src="../js/vue.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> let vm=new Vue({ el:'#app', data:{ attr:{ title:'新闻主题', content1:'新闻内容1', content2:'新闻内容2', } }, methods:{ }, computed:{ }, components:{ 'myContent':{ props:['title','content1','content2'], template:'<div><h4>{{title}}</h4><span>{{content1}}</span><span>{{content2}}</span></div>' } } }) </script> </body> </html>
现在这个实例所表现出来的就是一个对象里面有很多个属性,现在仅仅只有三个属性而已,我们的父组件绑定属性的时候就需要绑定三个属性,如果是100个或者1000个那么绑定组件的那个标签不是很长吗?和同事一起开发的话,你的同事看到那么长的代码肯定会怀疑人生的,所以为了解决这个问题我们将代码改写下面那样。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>props传递多个属性</title> </head> <body> <div id="app"> <my-content v-bind="attr"></my-content> </div> <script src="../js/vue.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> let vm=new Vue({ el:'#app', data:{ attr:{ title:'新闻主题', content1:'新闻内容1', content2:'新闻内容2', } }, methods:{ }, computed:{ }, components:{ 'myContent':{ props:['title','content1','content2'], template:'<div><h4>{{title}}</h4><span>{{content1}}</span><span>{{content2}}</span></div>' } } }) </script> </body> </html>
在这里我们使用v-bind将整个对象打包传递过去,这样一来就大大的减少了代码的冗余度了,那么你的同事肯定夸你小伙子不错,你就等着被领导表扬吧!(自己脑补出来的)
data必须是函数
为什么说data必须是一个函数呢?这个知识点在上一篇博客中没有提及到,现在的话我们来讨论一下,这个案例就足够说明了。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>data必须是函数</title> </head> <body> <div id="app"> <my-content></my-content> </div> <script src="../js/vue.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> Vue.component('myContent',{ data:{ msg:'hello world' }, template:'<span>{{msg}}</span>', }); let vm=new Vue({ el:'#app' }) </script> </body> </html>
结果:
控制台显示vue.js给出的警告也是提出data必须是一个函数,好的既然规定data必须是一个函数,那我们就按照它说的来做。看看结果如何
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>data必须是函数</title> </head> <body> <div id="app"> <my-content></my-content> <my-content></my-content> <my-content></my-content> </div> <script src="../js/vue.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> var data={ count:1 } Vue.component('myContent',{ data:function(){ return data; }, template:'<button @click="count+=1">{{count}}</button>', }); let vm=new Vue({ el:'#app' }) </script> </body> </html>
现在的话,我们的data是一个函数,但是解决一个问题的同时新的问题又出现了,点击任意一个按钮的时候发现其它按钮的值都会发生改变,这是因为我们引用了同一个对象,我们知道对象是引用传递的,所以为了改变这一种情况,我们让每个组件内部都有自己的状态而不会去干涉其它组件。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>data必须是函数</title> </head> <body> <div id="app"> <my-content></my-content> <my-content></my-content> <my-content></my-content> </div> <script src="../js/vue.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> Vue.component('myContent',{ data(){ return{ count:1 } }, template:'<button @click="count+=1">{{count}}</button>', }); let vm=new Vue({ el:'#app' }) </script> </body> </html>
结果:
我们在data中使用return返回新的对象,这样一来每一个组件都有自己本身的状态了,从而不会去影响其它的组件。
组件的校验
组件的校验从字面上看就是对父组件向子组件传递的信息中,子组件对父组件传递过来的信息进行验证,一方面是为了数据的安全性,另一方面给他人使用的时候也可以限制他人传递过来的数据的验证。
Vue.component('example', { props: { // 基础类型检测 (`null` 意思是任何类型都可以) propA: Number, // 多种类型 propB: [String, Number], // 必传且是字符串 propC: { type: String, required: true }, // 数字,有默认值 propD: { type: Number, default: 100 }, // 数组/对象的默认值应当由一个工厂函数返回 propE: { type: Object, default: function () { return { message: 'hello' } } }, // 自定义验证函数 propF: { validator: function (value) { return value > 10 } } } })
type可选:
- Sting
- Number
- Boolean
- Function
- Object
- Array
(1)示例一
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>组件的校验示例一</title> </head> <body> <div id="app"> <my-content :name="name" :age="age" :sex="sex"></my-content> </div> <script src="../js/vue.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> let vm=new Vue({ el:'#app', data:{ name:'小明', age:18, sex:'男' }, methods:{ }, computed:{ }, components:{ 'myContent':{ props:{ name:{ type:Number, required:true, }, age:{ type:Number, default:20, }, sex:{ type:String, default:'女' } }, template:'<div><span>{{name}}</span><span>{{age}}</span><span>{{sex}}</span></div>' } } }) </script> </body> </html>
结果:
我们对name做了验证规定,name的类型必须是Number,而传递过来的却是String,所以vue.js给出了警告,对name属性也要求是必须传递的参数,required:true,对sex给定默认值,当我们没有传递sex的时候,子组件中的sex默认值是女的,所以有了组件的这一校验,大大提高的数据的安全性。
(2)示例二
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>组件的校验示例二</title> </head> <body> <div id="app"> <my-content name="喜马拉雅" :size="8848" :is-boy="false" address="中国西藏"></my-content> </div> <script type="text/template" id="template1"> <div>姓名:{{name}}身高:{{size}}是否是男生:{{isBoy}}位置:{{address}}重量:{{weight.ton}}亿吨</div> </script> <script src="../js/vue.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> Vue.component('myContent',{ template:'#template1', props:{ name:{ type:String, //类型 required:true, //规定必填 }, size:Number, //规定整形 isBoy:Boolean, //规定布尔值 age:[Number,String],// 多种类型 address:{ type:String, default:'中国', validator:function(value){ return value.indexOf('中国')>=0 } }, weight:{ type:Object, default:function(){ return {ton:999999999} } }, } }) let vm=new Vue({ el:'#app', data:{ }, methods:{ }, computed:{ } }) </script> </body> </html>
结果:
子组件向父组件传递数据
我们知道,父组件是使用 props 传递数据给子组件,但如果子组件要把数据传递回去,应该怎样做?那就是自定义事件!每个 Vue 实例都实现了事件接口(Events interface),即:
- 使用
$on(eventName)
监听事件 - 使用
$emit(eventName)
触发事件
接下来我们一步一步对子组件向父组件传递数据进行讲解,先来看下一个特别简单的示例,这个需求是这样的,当我们改变子组件的count的时候,父组件的count也需要改变
(1)子组件实现功能
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>子组件向父组件传递数据</title> </head> <body> <div id="app"> <my-content :count="count"></my-content> 父组件的count:<span>{{count}}</span> </div> <script src="../js/vue.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> let vm=new Vue({ el:'#app', data:{ count:1 }, methods:{ }, computed:{ }, components:{ 'myContent':{ data(){ return{ ownCount:this.count } }, props:['count'], template:'<div>子组件的count:<span>{{ownCount}}</span><button @click="handleClick">add</button></div>', methods:{ handleClick(){ this.ownCount++; } } } } }) </script> </body> </html>
我们通过父组件向子组件传递了count值,然后子组件中的count值在点击之后会发生改变,但是这仅仅实现了子组件的值改变,并没有实现父组件中的count改变
(2)向父组件传递信息
需要向父组件传递信息的话,我们就需要子组件通知父组件,然后父组件在做相应的处理,而需要通知父组件我们就i需要用到this.$emit(‘事件名称’,值)。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>子组件向父组件传递数据</title> </head> <body> <div id="app"> <my-content :count="count" @add="handleAdd"></my-content> 父组件的count:<span>{{count}}</span> </div> <script src="../js/vue.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> let vm=new Vue({ el:'#app', data:{ count:1 }, methods:{ handleAdd(count){ this.count=count; } }, computed:{ }, components:{ 'myContent':{ data(){ return{ ownCount:this.count } }, props:['count'], template:'<div>子组件的count:<span>{{ownCount}}</span><button @click="handleClick">add</button></div>', methods:{ handleClick(){ this.ownCount+=10; this.$emit('add',this.ownCount); } } } } }) </script> </body> </html>
结果:
我们在子组件点击按钮的时候添加了this.$emit()来通知父组件,然后将需要注册的事件和每次改变的值传递了过去,这时父组件注册子组件传递过来的事件和值,然后将父组件中的值替换子组件传递过来的就可以了。
(3)优化子组件向父组件传递信息
其实我们可以做到让父组件来控制子组件中count值的变化,假设我们每次让子组件增加10,我们先来一个小案例
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>子组件向父组件传递数据</title> </head> <body> <div id="app"> <my-content :count="count" @add="handleAdd" v-bind="info"></my-content> 父组件的count:<span>{{count}}</span> <button @click="handleClick">点击</button> </div> <script src="../js/vue.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> let vm=new Vue({ el:'#app', data:{ count:1, info:{ name:'男', age:18 } }, methods:{ handleAdd(count){ this.count=count; }, handleClick(){ this.info.name='女' } }, computed:{ }, components:{ 'myContent':{ data(){ return{ ownCount:this.count } }, props:['count','name','age'], template:'<div><span>姓名:{{name}}</span><br/>子组件的count:<span>{{ownCount}}</span><button @click="handleClick">add</button></div>', methods:{ handleClick(){ this.ownCount+=10; this.$emit('add',this.ownCount); } } } } }) </script> </body> </html>
结果:
当我们点击按钮的时候发现父组件传递给子组件的姓名发生了改变,那么我们只要改变父组件中的count的值,那么子组件中count的值也会发生改变
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>子组件向父组件传递数据</title> </head> <body> <div id="app"> <my-content :count="count" @add="hanldeAdd"></my-content> 父组件中的count<span>{{count}}</span> </div> <script src="../js/vue.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> let vm=new Vue({ el:'#app', data:{ count:1 }, methods:{ hanldeAdd(count){ this.count+=count; } }, computed:{ }, components:{ 'myContent':{ data(){ return{ //ownCount=this.count; } }, props:['count'], template:'<div>子组件中的count<span>{{count}}</span><button @click="handleClick">add</button></div>', methods:{ handleClick(){ this.$emit('add',10) } } } } }) </script> </body> </html>
这样一来父子组件的简单通信就全部讲完了,接下来为了巩固一下父子组件之前通信的知识,我们来做一个任务清单
总结:子组件向父组件传递信息this.$emit(‘事件名称’,值)
(4)任务清单
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>任务清单</title> </head> <body> <div id="app"> 任务:<input type="text" v-model="newTask" @keyup.enter="addNew" placeholder="请输入您要完成的任务" /> <ul> <li is="todoImte" v-for="(item,index) of tasks" :title="item" @remove="removeItem(index)"></li> </ul> </div> <script src="../js/vue.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> Vue.component('todoImte',{ props:['title'], template:"<li>{{title}}<button @click='$emit(\"remove\")'>X</button></li>" }); let vm=new Vue({ el:'#app', data:{ newTask:'', tasks:['买一本书','写一次博客','和朋友一起吃饭'] }, methods:{ addNew(){ this.tasks.unshift(this.newTask); this.newTask=''; }, removeItem(index){ if(confirm('确定要删除吗?')){ this.tasks.splice(index); } } }, computed:{ } }) </script> </body> </html>
结果:
任务清单中,首先有个添加任务的框,然后通过addNew方法将任务添加到任务清单中,子组件中通过this.$emit()告诉父组件执行相应的方法,任务清单中,我们可以添加任务清单也可以删除任务清单,添加任务清单的话,没有涉及组件通信,二删除任务清单的时候,子组件通知父组件需要删除那个任务,然后将需要删除任务的索引传递过去,父组件根据传递过来的index进行删除。
总结
本篇博客主要讲了三个知识点,组件的校验,父组件向子组件传递信息(通过props),子组件向父组件传递信息(通过this.$emit),本篇博客讲解的内容也比较简单,但是我认为这仅仅也还是组件的开始,下一遍博客我们将会讲解vue-cli中的组件通信,毕竟这个才是重点,现在的组件通信也才是入门。欢迎大家在博客下方评论,我们一起交流。