Vue入门学习之技术分享-2(深入理解Vue组件)
栏目: JavaScript · 发布时间: 5年前
内容简介:继前几天学习了指令,属性学习后,这两天又深入学习了组件。每次学习过后发一篇文章,自己对知识点的记忆也更加深刻了,同时也希望自己的分享能够帮助到其他人上述代码将this is a row封装成一个名为row的Vue全局组件,并在tbody中引用了row组件,使用时完全符合html5页面规范table里面有tbody,tbody里面有tr,页面显示出来的结果如下,看起来也没有任何问题但是页面检查查看Elements时你会发现出错了,正常情况下tr应该在tbody但是现在却跟table同级。
继前几天学习了指令,属性学习后,这两天又深入学习了组件。每次学习过后发一篇文章,自己对知识点的记忆也更加深刻了,同时也希望自己的分享能够帮助到其他人
正文
使用组件的细节点
is的使用
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>组件中的细节点</title> <script src="../vue.js"></script> </head> <body> <div id="root"> <table> <tbody> <row></row> <row></row> <row></row> </tbody> </table> </div> <script> Vue.component('row', { template:'<tr><td>this is a row</td></tr>' }) var vm = new Vue({ el:'#root' }) </script> </body> </html> 复制代码
上述代码将this is a row封装成一个名为row的Vue全局组件,并在tbody中引用了row组件,使用时完全符合html5页面规范table里面有tbody,tbody里面有tr,页面显示出来的结果如下,看起来也没有任何问题
但是页面检查查看Elements时你会发现出错了,正常情况下tr应该在tbody但是现在却跟table同级。
这个BUG我们应该如何解决呢?这个时候我们就应该用到Vue给我们提供的is属性来解决这个问题。在tbody只能写tr那我们就写tr好了,但是我们写这个tr并不是真的显示tr而是想把row组件里面的内容显示进去。
<tbody> <tr is="row"></tr> <tr is="row"></tr> <tr is="row"></tr> </tbody> 复制代码
这个时候在tr后面加一个is属性让它等于row就好了,意思就是虽然我是tr但是我其实是一个row组件,这样既能够保证tr里面显示的是我们的row组件又保证我们符合h5的编码规范,程序不会有BUG。并且建议像ul、ol、select下面也不要直接使用row这个组件,因为有些浏览器会显示bug,所以就用Vue提供的is属性就好了
组件中的data
在组件中使用data如下
Vue.component('row', { data:{ content:'this is arow' }, template: '<tr><td>{{content}}</td></tr>' }) 复制代码
页面渲染报错
这是因为组件中的data必须是一个函数并且需要返回一个对象。之所以这样是因为子组件不像根组件一样只调用一次,它在很多地方都会被调用,而在每次调用都不希望和其他子组件产生冲突或者说每个子组件都应该有自己的数组。通过一个函数来返回一个对象的目的就是让每一个子组件都拥有独立的数组存储,这样就不会出现多个子组件相互影响的情况。如下,这样页面才能正常显示,初学Vue的小白,很容易忘记这一点然后页面疯狂报错,所以请记住这一基础知识点
data() { return { content: 'this is a row' } }, 复制代码
ref的使用
Vue不建议我们在代码里面去操作dom,但是在处理一些及其复杂的动作效果时,你不操作dom光靠Vue的数据绑定,有的时候处理不了这样的情况,所以在必要情况下需要操作dom。那么我们如何操作dom呢?我们需要通过ref这种引用的形式获取到进行dom的操作。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>组件中的细节点</title> <script src="../vue.js"></script> </head> <body> <div id="root"> <div ref='hello' @click="handleClick">hello world </div> </div> <script> var vm = new Vue({ el: '#root', methods: { handleClick: function() { console.log(this.$refs.hello) } }, }) </script> </body> </html> 复制代码
我们在div上添加了一个ref属性并命名为hello,并且绑定了一个名为handleClick的点击方法,在Vue实例的methods方法中打印了一串代码。
this.$refs.hello 复制代码
这个代码的意思就是整个Vue实例里面所有的引用里面的名为hello的引用,这个引用只的就是被修饰的div的DOM结点,所以下面的this.$refs.hello指向的就是相对应的div的dom结点,打印结果如下
所以在Vue当中我们可以通过ref来获取Dom结点,然后我们就可以通过结点获取innerHTML 接下来我们用这个功能做一个计数器的栗子。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>组件中的细节点</title> <script src="../vue.js"></script> </head> <body> <div id="root"> <!-- 当子组件触发change事件时,父组件能监听到这个事件并执行handleChange这个方法 --> <counter ref="one" @change="handleChange"></counter> <counter ref="two" @change="handleChange"></counter> <div>{{total}}</div> </div> <script> Vue.component('counter', { template: '<div @click="handleClick">{{number}}</div>', data() { return { number: 0 } }, methods: { handleClick: function() { this.number ++ // 子组件向父组件传值,这里的意思是当子组件触发事件时同时告诉外面触发一个事件 this.$emit('change') } }, }) var vm = new Vue({ el: '#root', data:{ total: 0 }, methods:{ handleChange: function() { this.total = this.$refs.one.number + this.$refs.two.number } } }) </script> </body> </html> 复制代码
上述代码中封装了一个counter组件实现了一个点击实现num++的功能,在html引用了两次counter组件。我们需要实现的是在父组件中计算两个counter组件中值的和。这时就需要在子组件上添加ref属性。然后再父组件中用 this.$refs.(ref名称)获取组件并通过.number获取每个引用组件各自的number值并相加实现求total的功能。
父子组子组件传值
父->子
父组件如何向子组件传值呢? Vue中父组件向子组件传都是通过属性的形式向子组件传值的
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>父子组件传值</title> <script src="../vue.js"></script> </head> <body> <div id="root"> <!-- count前面加了冒号,传递给子组件的就是数组,不加冒号传递给子组件的就是字符串,因为加了冒号后,后面的双引号的内容实际上是一个js表达式了就不是一个字符串了 --> <counter :count="0"></counter> <counter :count="1"></counter> </div> <script> var counter = { // 在子组件中写一个props(用来接收父组件传递过来的内容),接收完以后就可以在子组件中直接使用了 props:['count'], template: '<div>{{count}}</div>' } var vm = new Vue({ el: '#root', components:{ counter } }) </script> </body> </html> 复制代码
上面栗子,声明了一个局部counter组件并挂载到了vm根实例中并在页面引用了counter组件,其中 父组件 通过在counter组件中 添加属性 :count="0/1"(count前面加了冒号,传递给子组件的就是数组,不加冒号传递给子组件的就是字符串,因为加了冒号后,后面的双引号的内容实际上是一个js表达式了就不是一个字符串了)。那么 子组件 如何接收 父组件 的传值呢? 子组件 需要在内部写一个 props 用来 接收 父组件传递过来的内容。这样就能在子组件内部使用啦,这时候我们再修改一下子组件中的代码
var counter = { // 在子组件中写一个props(用来接收父组件传递过来的内容),接收完以后就可以在子组件中直接使用了 props:['count'], template: '<div @click="handleClick">{{count}}</div>', methods: { handleClick: function() { this.count ++ } }, } 复制代码
在子组件中添加一个方法实现点击+1的效果。你会发现页面上实现正确,但是打开控制台就有报错信息了
为什么呢?因为在Vue之中有一个 单项数据流 的概念,也就是说 父组件可以通过属性的形式向子组件传递参数 , 但是子组件只能使用父组件传递过来的内容而不能修改子组件传递过来的参数 。之所以Vue当中有单项数据流的概念, 原因 在于,一旦 子组件接收的 内容并 不是基础类型 的数据,而 是一个引用形式数据 的时候,你在 子组件改变了父组件传递过来的内容时 ,有可能接收的数据还被其他的组件所使用,所以当你 改变父组件传递过来的数据时还会影响其他的子组件。那么如何解决呢?
var counter = { // 在子组件中写一个props(用来接收父组件传递过来的内容),接收完以后就可以在子组件中直接使用了 props:['count'], data() { return { // 复制了一份count放到子组件自己的data中 number:this.count } }, template: '<div @click="handleClick">{{number}}</div>', methods: { handleClick: function() { this.number ++ } }, } 复制代码
我们需要在子组件的data中自定义一个number复制一份父组件传递过来的count,这样就可以在组件中使用子组件自己number了。这样控制台就不会报错啦。
子->父
子组件向父组件传值是通过**$emit()**方法来实现对外暴露的
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>父子组件传值</title> <script src="../vue.js"></script> </head> <body> <div id="root"> <counter :count="3" @inc="handleIncrease"></counter> <counter :count="2" @inc="handleIncrease"></counter> <div>{{total}}</div> </div> <script> var counter = { props:['count'], data() { return { // 复制了一份count放到子组件自己的data中 number:this.count } }, template: '<div @click="handleClick">{{number}}</div>', methods: { handleClick: function() { this.number = this.number + 2 // 每次触发事件时都告诉外部触发了一个inc事件,并且携带了一个参数(可以携带多个参数) this.$emit('inc',2) } }, } var vm = new Vue({ el: '#root', data:{ total: 5 }, components:{ counter }, methods:{ // 子组件触发inc事件时传递了一个参数,这时就可以在父组件中的方法中接收这个参数了 handleIncrease:function(num) { this.total+=num } } }) </script> </body> </html> 复制代码
代码中在子组件counter中的handleClick方法中通过this.$emit(inc,2)向父组件暴露了一个inc事件,并且携带了一个参数2(不仅仅能携带一个参数,它可以携带多个参数)。而在父组件中
<counter :count="3" @inc="handleIncrease"></counter> <counter :count="2" @inc="handleIncrease"></counter> 复制代码
通过handleIncrease来监听inc事件,在vm根实例中的methods方法中定义handleIncrease方法,由于子组件向父组件传递了参数,所以handleIncrease可以在方法中接收这个参数并使用。
组件参数校验与非Props特性
组件的参数校验
什么是组件的参数校验? 父组件向子组件传递了内容,那么子组件有权对这些内容进行约束,这些约束就是参数的校验。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>组件参数校验与非Props特性</title> <script src="../vue.js"></script> </head> <body> <div id="root"> <child content="hello world"></child> </div> <script> Vue.component('child', { props: { // content:String,//子组件接收到的content必须是String类型的,如果不是String类型那么当子组件用了content值得时候控制台将会发出警告。 // 如果需要给定多个类型则需要把类型放入数组中 // content:[ Number, String ], // 当然这些约束还可以更加复杂,实际上还可以跟一个对象 content:{ type:String, //type规定类型 required:true, //required则表示这个参数是必传的,如果父组件不传这个属性则会报错 default:'default value', //当required为false时(接收到的属性值不是必传的),设置default则会在父组件没传值的时候用默认值(default value)代替 validator:function(value) { return (value.length > 5 ) } } }, template:`<div> <p>{{content}}</p> </div>` }) var vm = new Vue({ el: '#root' }) </script> </body> </html> 复制代码
上面的代码中父组件向子组件传递了一个content属性。那么我们如何在子组件中对它进行 约束 呢?首先我们需要把prop定义成一个对象。最简单的就是只约束类型并且类型只有一个了。下面的这段代码的意思是子组件接收到的content必须是String类型的,如果不是String类型那么当子组件用了content值得时候控制台将会发出警告。
props: { conten:String } 复制代码
如果要给content规定多个类型呢?只需要在后面使用数组就行了
content:[ Number, String ] 复制代码
当然这些约束还可以更加复杂,实际上还可以跟一个对象。可以看下面这段代码以及其中的注释
content:{ type:String, //type规定类型 required:true, //required则表示这个参数是必传的,如果父组件不传这个属性则会报错 default:'default value', //当required为false时(接收到的属性值不是必传的),设置default则会在父组件没传值的时候用默认值(default value)代替 validator:function(value) { return (value.length > 5 ) } } }, 复制代码
非Props特性
什么叫做非Pops特性和Props特性呢? Props特性 指的是当你的父组件使用子组件的时候,通过属性属性向子组件传值的时候恰好子组件里面声明了对父组件传递过来的属性的接收。 非Pops特性 值的时父组件向子组件传递了一个属性,但是子组件并没有声明接收父组件传递过来的内容。
- 非Props第一个特点:如果在这种情况下使用了没接收的值页面就会报错,这是因为你的父组件向子组件传递了值,但是子组件并没有接收父组件传递过来的值。
- 第二个特点:这个属性会展示在子组件最外层的dom标签的html属性里面
给组件绑定原生事件
先看一段代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>给子组件绑定原生事件</title> <script src="../vue.js"></script> </head> <body> <div id="root"> <child @click="handleClick"></child> </div> <script> Vue.component('child', { template:` <div> Child </div> ` }) var vm = new Vue({ el:'#root', methods: { handleClick: function() { alert('点击了') } }, }) </script> </body> </html> 复制代码
上面的代码乍一眼看当我们点击child组件的dom时,会触发父组件上的点击事件方法。但是运行上面的代码时会发现在父组件中定义的点击事件是没有用的。这是怎么一回事呢?当给一个组件绑定一个事件的时候,实际上这个事件绑定的时一个自定义的事件,也就是说如果你的鼠标点击触发的事件,并不是绑定的click事件,如果想要触发自定义的click事件,必须在子组件里面对需要触发事件的dom元素进行事件的绑定,这个事件才是真正的原生事件。添加下面部分代码
Vue.component('child', { template:` <div @click="handleChildClick"> Child </div> `, methods:{ handleChildClick: function() { alert("child click") } } }) 复制代码
点击dom元素时child click 被打印出来了子组件的原生事件被触发了,而父组件的事件没有被触发
自定义事件如何触发再往上内容以已经用到过,我再这里再讲一遍
this.$emit('click') 复制代码
只需要在子组件methods中调用$click(因为自定义事件的名称为click,这里的名字必须与自定义事件名称保持一致),这样父组件中的事件就能被调用了,这样当点击child组件绑定了handleChildClick事件的dom元素就会再打印下面这条消息了
上面的代码是不是特别烦,每次绑定事件都要传递两次?那么有没有更简单的方法呢?肯定是有的。直接在自定义事件后加一个 native修饰符就可以了。回到最初无法实现点击效果的版本
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>给子组件绑定原生事件</title> <script src="../vue.js"></script> </head> <body> <div id="root"> <!-- 在组件上加一个native修饰符,表示监听的是原生的点击事件 --> <child @click.native="handleClick"></child> </div> <script> Vue.component('child', { template:` <div @click="handleChildClick"> Child </div> ` }) var vm = new Vue({ el:'#root', methods: { handleClick: function() { alert('点击了') } }, }) </script> </body> </html> 复制代码
在代码中的自定义事件上加上**.native**修饰就表示触发的是原生的点击事件了,这就是给组件绑定原生事件的方法了
<child @click.native="handleClick"></child> 复制代码
非父子组件之间的传值
一个网页可以拆分为很多部分,每一个部分就是代码中的组件
父子组件的传值问题上面已经讲过了(父->子-通过属性,子->父通-过事件触发)。那么如何在非父子组件之间传值呢?
- 借助Vue官方提供的数据层框架 vuex 来解决这个问题,当然vuex使用起来是有难度的。可能会在之后学完后的项目中运用,如果我学会了的话,这里就不讲了,因为我也不会
- 使用发 布订阅模式 来解决,发布订阅模式在Vue中也被称为 总线 。 这里需要实现一个点击一个组件的DOM元素,其兄弟组件也跟着变得栗子
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>非父子组件间的传值(Bus/总线/发布订阅模式/观察者模型)</title> <script src="../vue.js"></script> </head> <body> <div id="app"> <child content='Yu'></child> <child content='Han'></child> </div> <script> // 在Vue的prototype上挂载了一个名字叫做bus的属性,并且这个属性指向Vue()的实例,只要我们之后去调用new.vue或者创建组件的时候,每个组件都会有bus这个属性 // 因为每个组件或者每个Vue实例都是通过类来创建的,而我在vue的prototype上挂载了一个bus属性 Vue.prototype.bus = new Vue() Vue.component('child',{ props:{ content:String }, data() { return { selfcontent:this.content } }, template:'<div @click="handleClick">{{selfcontent}}</div>', methods: { handleClick:function() { this.bus.$emit('change',this.selfcontent) //因为bus是vue的实例,所以可以使用$emit } }, // 声明一个mounted生命钩子函数 mounted:function() { // 在钩子函数中this指向会发生改变所以需要做以下工作 var this_ = this this.bus.$on('change',function(msg) { this_.selfcontent = msg // })//因为bus是个实例所以有$on的方法,它就能监听到bus上面触发出来的事件(change),并且能接收带过来的参数 }, }) var vm = new Vue({ el: '#app' }) </script> </body> </html> 复制代码
- 在Vue的prototype上挂载了一个名字叫做bus的属性,并且这个属性指向Vue()的实例,只要我们之后去调用new.vue或者创建组件的时候,每个组件都会有bus这个属性因为每个组件或者每个Vue实例都是通过类来创建的,而我在vue的prototype上挂载了一个bus属性
- 在组件中使用this.bus. emit),并且携带需要的参数
this.bus.$emit('change',this.selfcontent) 复制代码
- 声明一个mounted生命钩子函数并且定义_this=this,因为钩子函数中this会发生改变
- 使用bus实例上的$on方法监听组件抛出的change事件并接收参数再对selfcontent 进行修改就可以了
this.bus.$on('change',function(msg) { this_.selfcontent = msg // })//因为bus是个实例所以有$on的方法,它就能监听到bus上面触发出来的事件(change),并且能接收带过来的参数 复制代码
在Vue中使用插槽(slot)
插槽的使用
当使用组件时需要往里面添加一些内容,不仅可以用父组件传值的方法,还可以利用插槽来实现
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Vue中的插槽(slot)</title> <script src="../vue.js"></script> </head> <body> <div id="app"> <child> <p>Yu</p> </child> </div> <script> Vue.component('child',{ template:`<div> <P>HELLO</P> <slot>默认内容</slot> </div>` }) var vm = new Vue({ el: '#app' }) </script> </body> </html> 复制代码
上面的代码在child组件中插入了p标签和Yu内容,如何在组件中接收这个内容呢只需要在模板中使用slot就行了。并且可以在slot标签中写上默认内容,当不传递插槽内容的时候则会显示默认内容。再来看一个
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Vue中的插槽(slot)</title> <script src="../vue.js"></script> </head> <body> <div id="app"> <contentx> <div class="header">header</div> <div class="footer">footer</div> </contentx> </div> <script> Vue.component('contentx',{ template:`<div> <slot></slot> <div class='content'>content</div> <slot></slot> </div>` }) var vm = new Vue({ el: '#app' }) </script> </body> </html> 复制代码
上面这段代码的目的是在页面上显示出一个header、一个content、一个footer。结果却是
为什么呢?因为slot是显示组件中添加的所有标签元素。
这时候嘿嘿嘿就需要用到我们稍微高大上一点的具名插槽了,将上面部分代码修改成这样
<div id="app"> <contentx> <div class="header" slot="header">header</div> <div class="footer" slot="footer">footer</div> </contentx> </div> <script> Vue.component('contentx',{ template:`<div> <slot name='header></slot> <div class='content'>content</div> <slot name='footer'></slot> </div>` }) var vm = new Vue({ el: '#app' }) 复制代码
- 给contentx组件内插入的标签元素添加一个solt属性并命名
- 在组件模板中使用slot时指明name也就是在1中添加的属性名。这样就可以哪里要用指哪里,妈妈再也不用担心我的学习的。
作用域插槽
当一个组件中某些标签元素是通过循环自身data中的某个数组来进行渲染的时候,而这个组件又需要给其他组件使用,这时这个组件中的模板样式就不能被写死,因为每个组件需要循环的次数是不一样的。或者组件中的某一部分需要通过外部传递进来的时候,这时候就需要用到作用域插槽了
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Vue中的作用域插槽</title> <script src="../vue.js"></script> </head> <body> <div id="app"> <child> <!-- 一定要在最外层套用一个template,这时固定写法,同时要写一个slot-scope="自定义名字" 它的意思是当子组件用slot的时候会往slot里面传递一个item数据,这样就可以使用item了,而item是放在slot-scope="自定义名字"当中--> <!-- 这个时候每一项应该显示成什么,就不由子组件决定了,而是父组件调子组件的时候给子组件去传递对应的模板 --> <template slot-scope="props"> <li>{{props.item}} - hello</li> </template> </child> </div> <script> Vue.component('child',{ data() { return { list:[1,2,3,4] } }, template:`<div> <ul> <slot v-for="item of list" :item=item></slot> </ul> </div>` }) var vm = new Vue({ el: '#app' }) </script> </body> </html> 复制代码
- 在child组件中的模板内的slot中传递item出去
- 在根组件中使用了child全局组件,并在里面使用了 作用域插槽 (必须用template包裹,这是固定写法),同时用 slot-scope 声明要从child组件中接收的数据(item)都放在哪,这里是props中。
- 在template中告诉child组件应该怎样渲染页面,这里是以h1标签的形式对数据进行展示。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- Bootstrap入门之组件
- Docker入门:核心组件
- Android组件化入门:一步步搭建组件化架构
- Typescript 入门构建一个 react 组件
- OpenStack 入门及三大存储组件浅析
- ReactNative入门教程-组件生命周期函数
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。