三、 深入理解Vue组件
一.使用组件的细节点。
1.table 里面使用组件时要注意,由于h5规定table的tbody下必须放<tr></tr>标签,但是我们在使用组件的时候标签名用的是自定义的<row></row>,这就导致了下图的现象。
代码:
[code]<body> <div id="root"> <table> <tbody> <row></row> <row></row> <row></row> </tbody> </table> </div> <script type="text/javascript"> //创建一个全局组件 Vue.component('row', { template: '<tr><td>This is a row</td></tr>' }) var app = new Vue({ el: '#root' }) </script> </body>
效果图和控制台:
可以看到,控制台里面<tr></tr>并没有包含在tbody中,这就是在table中使用组件需要注意的问题。解决办法如下:
[code]<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <script type="text/javascript" src="./vue/vue.js"></script> </head> <body> <div id="root"> <table> <tbody> <tr is='row'></tr> <tr is='row'></tr> <tr is='row'></tr> </tbody> </table> </div> <script type="text/javascript"> //创建一个全局组件 Vue.component('row', { template: '<tr><td>This is a row</td></tr>' }) var app = new Vue({ el: '#root' }) </script> </body> </html>
可以看出控制台效果图里面已经解决了这个问题:
同理,像ul ol select 这三个 最好子元素也用 li option 来表示,里面用 is='组件名'来解决。因为在有些浏览器上面也会出现上面一样的bug
2.注意,在子组件中data不能是一个数据,必须是函数。但是在根组件中,即我们下面创建的Vue实例里,data可以是数据。具体bug看代码:
[code] <div id="root"> <table> <tbody> <tr is='row'></tr> <tr is='row'></tr> <tr is='row'></tr> </tbody> </table> </div> <script type="text/javascript"> //创建一个全局组件,这是子组件 Vue.component('row', { data: { content: 'This is a row' }, template: '<tr><td>{{content}}</td></tr>' }) //这是根组件 var app = new Vue({ el: '#root' }) </script>
效果图中页面什么也没有呈现出来,因为我们在子组件中data里面是数据,而非函数。解决方法如下:
[code]<div id="root"> <table> <tbody> <tr is='row'></tr> <tr is='row'></tr> <tr is='row'></tr> </tbody> </table> </div> <script type="text/javascript"> //创建一个全局组件,这是子组件 Vue.component('row', { data: function(){ return { content: 'this is content' } }, template: '<tr><td>{{content}}</td></tr>' }) //这是根组件 var app = new Vue({ el: '#root' }) </script>
效果图:
3.Vue尽管不建议我们直接操作dom,但是有时候我们为了实现某些功能我们不得不操作dom。Vue提供了一种操作dom的方法,即ref,代码如下:
[code]<body> <div id="root"> <div ref='haha' @click='handBtn'>hello world</div> </div> <script type="text/javascript"> var app = new Vue({ el: '#root', methods: { handBtn: function(){ console.log(this.$refs.haha) } } }) </script> </body>
效果图:
想要打印dom里面的内容,在后面添加innerHTML即可。即console.log(this.$refs.haha.innerHTML);
小Demo,使用ref实现一个计数器的功能。
[code]<body> <div id="root"> <counter ref='one' @change='handChange'></counter> <counter ref='two' @change='handChange'></counter> <div>{{total}}</div> </div> <script type="text/javascript"> Vue.component('counter',{ template: '<div @click="handBtn">{{number}}</div>', data: function(){ return { number: 0 } }, methods: { handBtn: function(){ this.number++, //监听子组件数据是否变化 this.$emit('change') } } }) var app = new Vue({ el: '#root', data: { total: 0 }, methods: { handChange: function(){ this.total = this.$refs.one.number + this.$refs.two.number } } }) </script> </body>
效果图:
二.父子组件间的数据传递
1.父组件通过属性向子组件传值
[code]<body> <div id="root"> <counter :count='0'></counter> <counter :count='11'></counter> </div> </body> <script type="text/javascript"> //创建一个局部组件,注:局部组件需要在Vue实例中注册这个局部组件 var counter = { //父组件通过属性向子组件传值 props: ['count'], template: '<div>{{count}}</div>' } var root = new Vue({ el: '#root', //注册局部组建 components: { counter: counter } }) </script>
效果图:
单向数据流特性/*注意:子组件不能修改父组件传过来的值,即不能这么写:this.count ++。因为如果这个数据被好多组建使用的话,那么子组件修改父组件传过来的值就会导致其他的组件的值也发生改变。解决办法是通过将count这个值传给子组件本身数据里面的对象,通过改变这个对象也就改变了*/ 代码如下:
[code]<body> <div id="root"> <counter :count='0'></counter> <counter :count='11'></counter> </div> </body> <script type="text/javascript"> //创建一个局部组件,注:局部组件需要在Vue实例中注册这个局部组件 var counter = { //父组件通过属性向子组件传值 props: ['count'], //我们定义一个number,通过它来改变子组件的值 data: function(){ return { number: this.count } }, template: '<div @click="handBtn">{{number}}</div>', methods: { handBtn: function() { /*注意:子组件不能修改父组件传过来的值,即不能这么写:this.count ++。因为如果这个数据被好多组建使用的话,那么子组件修改父组件传过来的值就会导致其他的组件的值也发生改变。解决办法是通过将count这个值传给子组件本身数据里面的对象,通过改变这个对象也就改变了*/ this.number ++ } } } var vm = new Vue({ el: '#root', //注册局部组建 components: { counter: counter } }) </script>
2.子组件向父组件传值
[code]this.$emit('haha',2)
[code]<body> <div id="root"> <counter :count='1' @haha='handChange'></counter> <counter :count='2' @haha='handChange'></counter> <div>{{total}}</div> </div> </body> <script type="text/javascript"> var counter = { props: ['count'], data: function(){ return { number: this.count } }, template: '<div @click="handBtn">{{number}}</div>', methods: { handBtn: function() { this.number += 2, this.$emit('haha',2) } } } var vm = new Vue({ el: '#root', components: { counter: counter }, data: { total: 3 }, methods: { handChange: function(step){ this.total += step } } }) </script>
三.组件参数校验和非porps特性
1.校验
[code]<body> <div id="root"> <!-- :content='123',这样传过去的值是123,number型。如果传hello,要这么写content='hello' --> <row :content='123'></row> </div> <script type="text/javascript"> Vue.component('row',{ //意思是父组件传给子组件的数据必须是字符串 props: { content: String }, template: '<div>{{content}}</div>' }) var vm = new Vue({ el: '#root' }) </script> </body>
效果图:
即使页面显示了出来,但是可以看见控制台有警告,告诉我们子组件规定传过来的必须是字符串型,但是传递过来的是数值型。
注意 : 假如我们对传来的值要求可以是字符串,也可以是数值型,可以这么写content: [String,number]。content里面不光能用数组表示,还能用对象,代码如下:
[code]<body> <div id="root"> <!-- :content='123',这样传过去的值是123,number型 --> <row content="qwe"></row> </div> <script type="text/javascript"> Vue.component('row',{ //意思是父组件传给子组件的数据必须是字符串 props: { content: { type: String } }, template: '<div>{{content}}</div>' }) var vm = new Vue({ el: '#root' }) </script> </body>
效果同数组形式的写法。
想要让content这个属性是必须传的,可用required: true,代码如下:
[code]<body> <div id="root"> <row></row> </div> <script type="text/javascript"> Vue.component('row',{ //意思是父组件传给子组件的数据必须是字符串 props: { content: { type: String, //表示content这个属性是必传的,如果不传,控制台会报错 required: true } }, template: '<div>{{content}}</div>' }) var vm = new Vue({ el: '#root' }) </script> </body>
控制台:
默认值default:
[code]<body> <div id="root"> <row></row> </div> <script type="text/javascript"> Vue.component('row',{ props: { content: { type: String, required: false, default: 'default value' /*上面这句话的意思是row这个组件需要接收一个属性content,它不是必须传的,但是假如你不传,那么它有一个默认值,为default value。*/ } }, template: '<div>{{content}}</div>' }) var vm = new Vue({ el: '#root' }) </script> </body>
限制content的长度,validator
[code]<body> <div id="root"> <row content='hell'></row> </div> <script type="text/javascript"> Vue.component('row',{ props: { content: { type: String, validator: function(value){ return (value.length > 5); } /*上面的意思是子组件row要接收一个content,这个content必须是字符串型的,通过自定义校验器validator做一个校验,value指的就是传入的内容,要求传入的这个字符串的内容必须大于5*/ } }, template: '<div>{{content}}</div>' }) var vm = new Vue({ el: '#root' }) </script> </body>
效果图:
由于长度小于5,控制台会报错:
2.非props特性。
props特性:当父组件使用子组件的时候,通过属性向子属性传值的时候,恰好子组件里面声明了对父组件传递过来的一个接收。这么写的特性称为props特性。特点是:1.它在父组件上面声明的属性,在控制台中的dom里是不会显示出来的。2.当父组件将content传递给子组件以后,子组件就能够使用在差值表达式中或者在this.content中使用它。
非props特性:父组件向子组件传递了一个属性,但是子组件并没有props这一块的内容,也就是子组件并没有声明要接收父组件传递过来的内容。特点:当模板没用content传值的时候,而是直接在模板上写值,那么父组件上面声明的属性会在dom上面显示出来。代码如下:
[code]<body> <div id="root"> <row content='hell'></row> </div> <script type="text/javascript"> Vue.component('row',{ template: '<div>hello</div>' }) var vm = new Vue({ el: '#root' }) </script> </body>
效果图:
四.给组件绑定原生事件
[code]<body> <div id="root"> <row @click='handClick'></row> </div> <script type="text/javascript"> Vue.component('row',{ template: '<div>Child</div>' }) var vm = new Vue({ el: '#root', methods: { handClick: function(){ alert('click') } } }) </script> </body>
效果是:当我们点击child的时候没反应。原因是我们绑定的是一个自定义的事件,解决办法就是不在父组件上绑定,我们在子组件上绑定,代码如下:
[code]<body> <div id="root"> <row></row> </div> <script type="text/javascript"> Vue.component('row',{ template: '<div @click="handClick">Child</div>', methods: { handClick: function(){ alert('click') } } }) var vm = new Vue({ el: '#root' }) </script> </body>
但是,我们想要触发原生的事件可以这么写:
[code]<body> <div id="root"> <row @click='hand'></row> </div> <script type="text/javascript"> Vue.component('row',{ template: '<div @click="handClick">Child</div>', methods: { handClick: function(){ this.$emit('click') } } }) var vm = new Vue({ el: '#root', methods: { hand: function(){ alert(1) } } }) </script> </body>
想要直接触发父组件的事件可以这么写:
[code]<body> <div id="root"> <row @click.native='hand'></row> <!-- 给组建绑定原生的事件,可以在其后面加一个native修饰符就可以了 --> </div> <script type="text/javascript"> Vue.component('row',{ template: '<div>Child</div>' }) var vm = new Vue({ el: '#root', methods: { hand: function(){ alert(1) } } }) </script> </body>
五.非父子组件的传值。
利用总线机制(/Bus/发布订阅模式/观察者模式)解决非父子组件之间进行传值的问题。
[code]<body> <div id="root"> <child content='Dell'></child> <child content='Lee'></child> </div> <script type="text/javascript"> /*往Vue的prototype上挂载一个名字叫做bus的属性,这个属性指向一个Vue的实例,只要我们之后调用一个vue的实例,或创建一个组件的时候,每一个组件上都会有bus这个属性*/ Vue.prototype.bus = new Vue() Vue.component('child',{ props: { content: String }, template: '<div @click="handClick">{{content}}</div>', methods: { handClick: function(){ /*this.bus是存在的,因为我们上面挂载了一个bus的属性,而这个属性是指向一个Vue的实例的,所以就会有一个$emit这个方法,它向外触发事件,同时携带了一个this.content的数据。*/ this.bus.$emit('change', this.content) } }, //我们借助mounted这个生命周期钩子,也就是这个组件被挂载的时候执行的函数 mounted: function(){ /*this.bus是存在的,因为我们上面挂载了一个bus的属性,而这个属性是指向一个Vue的实例的,所以就会有一个$on这个方法,他就能监听到bus上触发出来的事件*/ this.bus.$on('change', function(msg){ alert(msg) }) } }) var vm = new Vue({ el: '#root' }) </script>
点击的时候弹出两次弹窗的原因是:在一个child组件去触发事件的时候,其实这两个child组件都进行了同一个事件的监听,所以两个child都会去弹msg
[code]<body> <div id="root"> <child content='Dell'></child> <child content='Lee'></child> </div> <script type="text/javascript"> Vue.prototype.bus = new Vue() Vue.component('child',{ props: { content: String }, template: '<div @click="handClick">{{content}}</div>', methods: { handClick: function(){ this.bus.$emit('change', this.content) } }, mounted: function(){ this.bus.$on('change', function(msg){ this.content = msg }) } }) var vm = new Vue({ el: '#root' }) </script> </body>
此时我们点击Dell的时候下面的Lee并没有变成Dell,原因是function里面Dell的作用域发生了变化,解决办法是
把this做一个保存。即 var this_ = this
[code]<body> <div id="root"> <child content='Dell'></child> <child content='Lee'></child> </div> <script type="text/javascript"> Vue.prototype.bus = new Vue() Vue.component('child',{ props: { content: String }, template: '<div @click="handClick">{{content}}</div>', methods: { handClick: function(){ this.bus.$emit('change', this.content) } }, mounted: function(){ var this_ = this this.bus.$on('change', function(msg){ this_.content = msg }) } }) var vm = new Vue({ el: '#root' }) </script> </body>
此时有效果:
但是控制台会报错:
原因是根据单向数据流,子组件不能改变父组件的值,解决办法:
[code]<body> <div id="root"> <child content='Dell'></child> <child content='Lee'></child> </div> <script type="text/javascript"> Vue.prototype.bus = new Vue() Vue.component('child',{ data: function(){ return { haha: this.content } }, props: { content: String }, template: '<div @click="handClick">{{haha}}</div>', methods: { handClick: function(){ this.bus.$emit('change', this.haha) } }, mounted: function(){ var this_ = this this.bus.$on('change', function(msg){ this_.haha = msg }) } }) var vm = new Vue({ el: '#root' }) </script> </body>
此时有效果,而且控制台也不会报错。
这样我们就实现了兄弟组件之间通过bus总线实现传值,同理,别的非父子组件之间也可以通过bus总线的方法实现传值。
六.在Vue中使用插槽
想要父组件向子组件传递一个dom元素
[code]<body> <div id="root"> <row content='<p>Dell</p>'></row> </div> <script type="text/javascript"> Vue.component('row',{ props: ['content'], template: '<div><p>hello</p>{{content}}</div>' }) var vm = new Vue({ el: '#root' }) </script> </body>
效果:
p标签被转译了,改进方法:
[code]<body> <div id="root"> <row content='<p>Dell</p>'></row> </div> <script type="text/javascript"> Vue.component('row',{ props: ['content'], template: '<div><p>hello</p><div v-html="this.content"></div></div>' }) var vm = new Vue({ el: '#root' }) </script> </body>
效果图:
但是这么写也就有一个弊端,p标签是被div包裹着的,我们是不希望p被包裹着,在这里我们将包裹他的div换成模板占位符<template v-html="this.content"></template>也不行。所以通过Content
传值,想要使用里面的p标签,外面必须包裹着一个div才能用。而且如果content里面有很多的Dom,那么代码将会很复杂。
所以当子组件有一部分内容是根据父组件传递过来的dom进行显示的时候,这个时候我们可以不使用这种语法。vue提供了一种插槽。
[code]<body> <div id="root"> <row> <p>Dell</p> </row> </div> <script type="text/javascript"> Vue.component('row',{ //slot显示的就是父组件插入的内容,即<p>Dell</p> template: '<div><slot></slot></div>' }) var vm = new Vue({ el: '#root' }) </script> </body>
效果图:
同时slot还有一些新的特性,
[code]<body> <div id="root"> <row> </row> </div> <script type="text/javascript"> Vue.component('row',{ template: '<div><slot>默认内容</slot></div>' }) var vm = new Vue({ el: '#root' }) </script> </body>
即当我们父组件没有插入内容的时候,slot他有个默认内容会显示在页面上。
我们如果想在header和footer部分都插入插槽:
[code]<body> <div id="root"> <row> <div class="header">header</div> <div class="footer">footer</div> </row> </div> <script type="text/javascript"> Vue.component('row',{ template: '<div><slot></slot><div class="content">content</div><slot></slot></div>' }) var vm = new Vue({ el: '#root' }) </script> </body>
效果图:
这不是我们想要的效果,修改方法:
[code]<body> <div id="root"> <row> <div class="header" slot="header">header</div> <div class="footer" slot="footer">footer</div> </row> </div> <script type="text/javascript"> Vue.component('row',{
[code]//多行字符串可以这么写` `。如下:
[code] template: `<div> <slot name='header'></slot> <div class="content">content</div> <slot name='footer'></slot> </div>` }) var vm = new Vue({ el: '#root' }) </script> </body>
效果图:
上面代码中,slot="header",这种写法我们叫做具名插槽。插槽只有一个,但是具名插槽可以有多个。具名插槽也能有默认值。
七.Vue中的作用域插槽。使用场景:当子组件做循环,或者某一部分他的dom结构应该由外部传递过来的时候,我们去用作用域插槽。
子组件要实现一个功能,去循环显示一个列表,在子组件里面定义一组数据。按以往的写法从下面例子看看效果:
[code]<body> <div id="root"> <row> </row> </div> <script type="text/javascript"> Vue.component('row',{ data: function(){ return { list: [1, 2, 3, 4] } }, template: `<div> <ul> <li v-for='item of list'>{{item}}</li> </ul> </div>` }) var vm = new Vue({ el: '#root' }) </script> </body>
效果图:
但是我们想实现一个功能,让父组件来决定我们如何显示这个列表:
[code]<body> <div id="root"> <row> <template slot-scope="props"> <h1>{{props.item}}</h1> </template> </row> </div> <script type="text/javascript"> Vue.component('row',{ data: function(){ 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: '#root' }) </script> </body>
效果图:
解释一下上面的代码:首先,父组件调用子组件的时候,给子组件传了一个作用域插槽,作用域插槽必须是template开头和结尾,同时这个插槽要声明我要从子组件接收的数据都放在哪,用
[code]slot-scope="props"来声明。
都放在props里,(props是自己定义的名字),然后再告诉子组件模板的信息,该如何展示,上面代码是以一个h1标签的形式对数据展示。子组件通过这个作用域插槽向父组件传数据。
八.动态组件和v-once指令。
看下面代码:
[code]<body> <div id="root"> <child-one></child-one> <child-two></child-two> <button>change</button> </div> <script type="text/javascript"> Vue.component('child-one',{ template: '<div>child-one</div>' }) Vue.component('child-two',{ template: '<div>child-two</div>' }) var vm = new Vue({ el: '#root' }) </script> </body>
效果图:
我们想实现一个功能,点击change,一会显示child-one,一会显示child-two,
[code]<body> <div id="root"> <child-one v-if="type ==='child-one'"></child-one> <child-two v-if="type ==='child-two'"></child-two> <button @click="handClick">change</button> </div> <script type="text/javascript"> Vue.component('child-one',{ template: '<div>child-one</div>' }) Vue.component('child-two',{ template: '<div>child-two</div>' }) var vm = new Vue({ el: '#root', data: { type: 'child-one' }, methods: { handClick: function(){ this.type = this.type === "child-one" ? "child-two" : "child-one" } } }) </script> </body>
效果图:
除了用这种方法实现,我们还可以用动态组件的形式来编写这段代码:
[code]<body> <div id="root"> <!-- component这个标签就是vue自带的标签,它指的就是动态组件 --> <component :is="type"></component> <button @click="handClick">change</button> </div> <script type="text/javascript"> Vue.component('child-one',{ template: '<div>child-one</div>' }) Vue.component('child-two',{ template: '<div>child-two</div>' }) var vm = new Vue({ el: '#root', data: { type: 'child-one' }, methods: { handClick: function(){ this.type = this.type === "child-one" ? "child-two" : "child-one" } } }) </script> </body>
效果和上面一样。动态组件的意思就是它会根据is里面数据的变化自动的加载不同的组件,比如说一开始的时候是child-one,这个时候动态组建就会去显示child-one这个组件,当我点击按钮了,child-one变成了child-two,此时is发现动态组建里面的数据变成了child-two,那他就会自动的child-one销毁掉,去显示child-two。
v-once指令。上面的动态组件每一次都要去销毁组件这种做法其实是会耗费一定性能的。
[code]<body> <div id="root"> <component :is="type"></component> <button @click="handClick">change</button> </div> <script type="text/javascript"> Vue.component('child-one',{ template: '<div v-once>child-one</div>' }) Vue.component('child-two',{ template: '<div v-once>child-two</div>' }) var vm = new Vue({ el: '#root', data: { type: 'child-one' }, methods: { handClick: function(){ this.type = this.type === "child-one" ? "child-two" : "child-one" } } }) </script> </body>
效果和上面一样,但是通过添加v-once指令,模板里面的内容第一次展示的时候就把它放在了内存里,这样就不需要销毁,再创建,这样提高了性能。在vue中通过v-once指令可以有效提高静态内容的展示效率。
阅读更多
- 深入理解Vue 组件之间传值
- 深入理解Vue父子组件通讯的属性和事件
- 深入理解Vue.js组件!
- 深入理解Vue生命周期、手动挂载及挂载子组件
- 深入理解Vue父子组件生命周期执行顺序及钩子函数
- 深入理解React中es6创建组件this的方法
- 深入理解基于vue-cli的vuex配置
- 三、基本组件(二)深入理解Service
- 深入理解React 高阶组件
- 模拟源码深入理解Vue数据驱动原理(2)
- 深入理解 Tomcat(六)源码剖析Tomcat 启动过程----生命周期和容器组件
- 深入浅析Vue全局组件与局部组件的区别
- Vue组件理解
- 三、基本组件(三)深入理解BroadcastReceiver
- 深入理解Vue.js源码之事件机制
- 深入对Vue.js $watch方法的理解
- 自定义组件<六>:深入理解ViewGroup
- 深入理解Vue官方文档梳理之全局API
- 深入理解vue中的$set
- 深入理解C#编程中的组件-事件-委托