组件需要注册后才可以使用。
Vue.component('my-component',{
template:'<div>这是组件内容</div>'
});
局部注册组件
var Child = {
template:'<div>局部注册组件内容</div>'
} var app = new Vue({
el:"#app",
components: {
'my-component':Child
}
})
Vue组件模板在某些情况下会受到限制,比如table,ul,ol.select等,这时可以类似这样:
<table>
<tbody is="my-component"></tbody>
</table>
还可使用数据绑定,但data必须是函数:
Vue.component('my-component',{
template:'<div>{{message}}</div>',
data:function(){
return {
message:'局部注册内容,data方法return'
}
}
})
使用props传递数据
<my-component message="来自父组件的信息"></my-component>
Vue.component('my-component',{
props:['message'],
template:'<div>{{message}}</div>'
})
父组件动态数据:
<input type="text" v-model="message"/>
<my-component :message="message"></my-component>
Vue.component('my-component',{
props:['message'],
template:'<div>{{message}}</div>'
}) var app = new Vue({
el:"#app",
data:{
message:''
} })
当输入框输入时,子组件接收到的props "message" 也会实时响应,并更新组件模板。
注意:
如果直接传递数字,布尔值,数组,对象,而不是用v-bind,传递的是字符串。
<my-component message="[1,2,3]"></my-component>
<my-component :message="[1,2,3]"></my-component>
Vue.component('my-component',{
props:['message'],
template:'<div>{{message.length}}</div>'
})
此时上一个会输出字符串的长度7,下面才是输出数组的长度3。
单向数据流
父组件传递初始值进来,子组件作为初始值保存,在自己的作用域下可随意修改。
<div id="app">
<my-component :init-count="1"></my-component>
</div> <script type="text/javascript"> Vue.component('my-component',{
props:['initCount'],
template:'<div>{{count}}</div>',
data:function(){
return {
count:this.initCount+1
}
}
}) var app = new Vue({
el:"#app",
data:{
message:''
} })
</script>
<div id="app">
<my-component :width="100"></my-component>
</div> <script type="text/javascript"> Vue.component('my-component',{
props:['width'],
template:'<div :style="style">组件内容</div>',
computed:{
style:function(){
return {
width:this.width+"px",
background:'red'
}
}
}
}) var app = new Vue({
el:"#app",
data:{
message:''
} })
</script>
子组件传递数值到父组件:
<div id="app">
<p>总数:{{total}}</p>
<my-component @increase="handleGetTotal" @reduce="handleGetTotal"></my-component>
</div>
<script type="text/javascript">
Vue.component('my-component',{
template:'<div><button @click="handleIncrease">+1</button><button @click="handleReduce">-1</button></div>',
data:function(){
return {
counter: 0
}
},
methods:{
handleIncrease:function(){
this.counter++;
this.$emit('increase',this.counter);
},
handleReduce:function(){
this.counter--;
this.$emit('reduce',this.counter);
}
}
});
var app = new Vue({
el:"#app",
data:{
total:0
},
methods:{
handleGetTotal:function(total){
this.total=total
console.log(total)
}
} });
在子组件定义两个事件,改变自身数据的同时使用$emit来触发父组件的自定义事件名称,从而触发对应的方法,再把改变的数据通过方法传到父组件的方法,从而改变父组件的数据。
使用v-model
<div id="app">
<p>总数:{{total}}</p>
<my-component v-model="total"></my-component>
</div>
<script type="text/javascript">
Vue.component('my-component',{
template:'<div><button @click="handleIncrease">+1</button></div>',
data:function(){
return {
counter: 0
}
},
methods:{
handleIncrease:function(){
this.counter++;
this.$emit('input',this.counter);
}
}
});
var app = new Vue({
el:"#app",
data:{
total:0
} });
</script>
这次区别是$emit通知的是特殊的input,但是并没有在组件上使用@input,这是因为v-model,这也可以称作是一个语法糖。
因为上面的示例可以间接实现:
<my-component @input="total"></my-component>
v-model还可以用来创建自定义的表单输入组件,进行数据双向绑定:
<div id="app">
<p>总数:{{total}}</p>
<my-component v-model="total"></my-component>
<button @click="handleReduce">-1</button>
</div>
<script type="text/javascript">
Vue.component('my-component',{
props:['value'],
template:'<input :value="value" @input="updateValue" />',
methods:{
updateValue:function(event){
this.$emit('input',event.target.value);
}
}
});
var app = new Vue({
el:"#app",
data:{
total:0
},
methods:{
handleReduce:function(){
this.total--;
}
} });
</script>
子组件input改变value值后出发updateValue,通过$emit触发特殊的input事件,传值输入的value值,改掉父组件的total,父组件的handleReduce方法改变total后,由于是双向绑定,所以子组件的value值也随着改变。
这需要满足两个条件:
1.接收一个props的value值,
2.更新value触发特殊的input事件
非父子组件通信
在vue1.x中是采用$dispatch()和$broadcast()这两个方法。$dispatch()由于向上级派发事件,只要是它的父级,都可在vue实例的events选项内接收。
在vue2.x中废弃了上述两种方法。在vue2.x中推荐使用一个空的vue实例作为*事件总线(bus),也就是一个中介。
如下:
<div id="app">
<p>信息:{{message}}</p>
<my-component></my-component>
</div>
<script type="text/javascript">
var bus = new Vue();
Vue.component('my-component',{
template:'<button @click="handleEvent"></button>',
methods:{
handleEvent:function(event){
bus.$emit('on-message','来自组件component的内容');
}
}
});
var app = new Vue({
el:"#app",
data:{
message:''
},
mounted:function(){
var _this = this;
bus.$on('on-message',function(msg){
_this.message=msg;
})
} });
</script>
定义一个空的vue实例当做中间人,在钩子函数mounted中监听了来自bus的事件on-message,在组件中会点击按钮通过bus把事件on-message发出去,此时app会接收到来自bus的事件。
除了中间介这种方式外,还有两种方法可实现组件间的通信:父链和子组件索引。
父链
子组件中使用this.$parent可直接访问父实例或组件,父组件也可以通过this.$children访问它所有子组件。
<div id="app">
<p>信息:{{message}}</p>
<my-component></my-component>
</div>
<script type="text/javascript">
Vue.component('my-component',{
template:'<button @click="handleEvent">通过$parent改变信息内容</button>',
methods:{
handleEvent:function(event){
this.$parent.message='子组件通过$parent改变了信息内容';
}
}
});
var app = new Vue({
el:"#app",
data:{
message:''
} });
</script>
子组件内通过this.$parent.message直接修改信息内容,正常使用不推荐使用。因为这样使得父子组件紧耦合,只看父组件,很难理解父组件状态,因为它可能被任意组件修改。最好还是通过props和$emit来通信。
子组件索引
<div id="app">
<button @click="handleRef">通过red获取子组件实例</button>
<my-component ref="Mycom"></my-component>
</div>
<script type="text/javascript">
Vue.component('my-component',{
template:'<div>子组件</div>',
data:function() {
return {
message:'子组件内容'
}
}
});
var app = new Vue({
el:"#app",
methods:{
handleRef:function(){
var msg = this.$refs.Mycom.message;
console.log(msg)
}
} });
</script>
父组件内的内容通过this.$refs.Mycom找到子组件,并输出了子组件的内容。
使用slot分发内容
当需要让组件组合使用,混合父组件发的内容与子组件的模板时,就会用到slot,这个过程叫做内容分发。
slot用法
单个slot: 在子组件标签内的素有内容替代子组件的<slot>标签及它的内容。如下:
<div id="app">
<child-component>
<p>分发的内容</p>
<p>更多分发的内容</p>
</child-component>
</div>
<script type="text/javascript">
Vue.component('child-component',{
template:'<div><slot><p>如果没有父组件插入内容,我将默认出现</p></slot></div>',
});
var app = new Vue({
el:"#app",
});
</script>
当父组件没有插入分发的内容,即上述代码没有
<p>分发的内容</p>
<p>跟多分发的内容</p>
的时候,子组件将默认显示子组件里slot的内容,如果父组件里插入了分发的内容,则将替换掉子组件里slot标签里的全部内容。
具名slot
给slot元素指定一个name后可以分发多个内容,具名slot可以与单个slot共存。
如下:
<div id="app">
<child-component>
<h2 slot="header">标题</h2>
<p>正文内容</p>
<p>更多正文内容</p>
<div slot="footer">底部信息</div>
</child-component>
</div>
<script type="text/javascript">
Vue.component('child-component',{
template:`<div class="container">
<div class="header">
<slot name="header"></slot>
</div>
<div class="main">
<slot></slot>
</div>
<div class="footer">
<slot name="footer"></slot>
</div>
</div>`,
});
var app = new Vue({
el:"#app",
});
</script>
在父组件上面可以具名插入slot的name,然后子组件会更具name插入具体的内容。
作用域插槽
是一种特殊的slot使用一个可以复用的模板替换已渲染的元素。
<div id="app">
<child-component>
<template scope="props">
<p>来自父组件的内容</p>
<p>{{props.msg}}</p>
</template>
</child-component>
</div>
<script type="text/javascript">
Vue.component('child-component',{
template:`<div class="container">
<slot msg="来自子组件的内容"></slot>
</div>`,
});
var app = new Vue({
el:"#app",
});
</script>
子组件模板,在slot元素上有一个类似pros传递数据给组件的写法 msg="XXX",将数据传到了插槽。父组件使用了template且拥有一个scope="props"的特性,这里的props知识一个临时变量,通过临时变量props来访问来自子组件的数据msg。渲染的最终结果为:
<div id="app">
<div class="container">
<p>来自父组件的内容</p>
<p>来自子组件的内容</p>
</div>
</div>
作用域插槽具代表性的用例是列表组件,允许组件自定义应该如何渲染列表的每一页。
<div id="app">
<my-list :book="books">
<!--作用域插槽也可以是具名的slot-->
<template slot="book" scope="props">
<li>{{props.bookName}}</li>
</template>
</my-list>
</div>
<script type="text/javascript">
Vue.component('my-list',{
props:{
books:{
type:Array,
default:function(){
return [];
}
}
},
template:'<ul><slot name="book" v-for="book in books" :bookName="book.name"></slot></ul>',
});
var app = new Vue({
el:"#app",
data:{
books:[
{name:'js'},
{name:'css'},
{name:'html'}
] }
});
</script>
子组件my-list接收一个来自父级的prop数组books,并且将它在name为book的slot上使用v-for指令循环,同时暴露一个变量bookName。
访问slot
vue2.x提供了用来访问被slot分发的内容的方法$slots。
<div id="app">
<my-component>
<h2 slot="header">标题</h2>
<p>正文内容</p>
<div slot="footer">底部信息</div>
</my-component>
</div>
<script type="text/javascript">
Vue.component('my-component',{
template:`<div class="container">
<div class="header">
<slot name="header"></slot>
</div>
<div class="main">
<slot></slot>
</div>
<div class="footer">
<slot name="footer"></slot>
</div>
</div>`,
mounted:function(){
var header = this.$slots.header;
var main = this.$slots.default;
var footer = this.$slots.footer; console.log(footer[0].elm.innerHTML)
}
});
var app = new Vue({
el:"#app",
});
</script>
在子组件钩子函数中能用$slots得到对应的slot。
组件的高级用法
递归组件
组件可以在它的模板内调用自己,只要给组件设置name属性就可以。
<div id="app">
<my-component :count="1"></my-component>
</div>
<script type="text/javascript">
Vue.component('my-component',{
name:'my-component',
props:{
count:{
type:Number,
default:function(){
return 1;
}
}
},
template:'<div class="child"><my-component :count="count+1" v-if="count<3"></my-component></div>', });
var app = new Vue({
el:"#app",
});
</script>
设置name后就可以递归使用了,不过需要一个限制条件,不然会报错。
内联模板
vue提供了一个内敛模板的功能,在使用组件时,给组件标签使用inline-template特性,组件就会把它的内容当做模板,而不是把它当内容分发。
<div id="app">
<my-component inline-template>
<div>
<h2>父组件定义子组件模板</h2>
<p>{{message}}</p>
<p>{{msg}}</p>
</div>
</my-component>
</div>
<script type="text/javascript">
Vue.component('my-component',{
data:function(){
return {
msg:'子组件内容'
}
} });
var app = new Vue({
el:"#app",
data:{
message:'父组件内容'
}
});
</script>
渲染成:
<div>
<h2>父组件定义子组件模板</h2>
<p>父组件内容</p>
<p>子组件内容</p>
</div>
使用内联模板会导致作用域混淆,应避免使用。
动态组件
vue.js提供了一个特殊的元素<component>用来动态挂载不同的组件,使用is特性来选择挂载的组件。
<div id="app">
<component :is="currentView"></component>
<button @click="handleChangeView('A')">切换到A</button>
<button @click="handleChangeView('B')">切换到B</button>
<button @click="handleChangeView('C')">切换到C</button>
</div>
<script type="text/javascript">
var app = new Vue({
el:"#app",
components:{
comA:{
template:'<div>A模板</div>'
},
comB:{
template:'<div>B模板</div>'
},
comC:{
template:'<div>C模板</div>'
},
},
data:{
currentView:'comA',
},
methods:{
handleChangeView:function (component){
this.currentView='com'+component
}
}
});
</script>
动态地改变currentView就可以切换视图。
异步组件
当工程足够大,使用组件足够多时,是时候考虑下性能了,因为一开始把所有组件都加载时时一笔开销。vue.js允许将组件定义为一个工厂函数,动态地解析组件。
<div id="app">
<my-component></my-component>
</div>
<script type="text/javascript">
Vue.component('my-component',function(resolve,reject){
setTimeout(()=>{
resolve({
template:'<div>我是异步渲染的</div>'
})
},2000)
});
var app = new Vue({
el:"#app",
});
</script>
$nextTick
当v-if,动态切换显示时,直接去取div的内容时获取不到的,因为此时div还没有被创建出来。这里涉及到一个vue的重要概念:异步更新队列。
vue在观察到数据变化时并不是直接更新DOM,而是开启一个队列,并缓冲在同一事件循环中发生的数据变化。
$nextTick就是用来知道DOM什么时候更新好的。
X-template
没有使用webpack,gulp等工具,组件内容复杂,如果都在javascript中拼接字符串,效率很低,vue提供了另一种定义模板的方式,在script标签使用text/template类型,指定一个ID,将这个id赋给template。
<div id="app">
<my-component></my-component>
<script type="text/x-template" id="my-component">
<div>这是组件内容</div>
</script>
</div>
<script type="text/javascript">
Vue.component('my-component',{
template:"#my-component"
});
var app = new Vue({
el:"#app",
});
</script>
vue的初衷不是滥用它,因为它将模板和组件的其他定义隔离了。