1.组件使用方法
(1)全局注册组件(要在父实例使用组件,就需要在父实例创建前注册组件)
<div id="app">
<my-component></my-component>
</div>
<script>
Vue.component('my-component',{
template:'<div>这里是组件的内容</div>' //要注意template的DOM结构必须被一个元素包裹,不带<div></div>无法渲染
});
var app = new Vue({
el:'#app'
})
</script>
(2)局部注册组件
在vue实例中,可以用components选项注册局部组件,该组件只在该实例作用域下有效(组件中也可以用components注册组件,使组件可以嵌套)
<div id="app">
<my-component></my-component>
</div>
<script>
var Child = {
template:'<div>局部注册组件的内容</div>'
}
var app = new Vue({
el:'#app',
components:{
'my-component':Child
}
})
</script>
(3)组件中的选项除了template之外,还有data,computed,methods等
但组件内的data与实例中的data不同(data必须是函数,然后将数据return出去)
//vue实例中的data
var app = new Vue({
el:'#app',
data:{
message:'a'
}
})
//组件内的data
Vue.component('my-component',{
template:'<div>{{message}}</div>',
data(){
return {
message:'a'
}
}
})
(4)JavaScript对象是引用关系,如果return出的对象引用了外部的对象,那么这个对象是共享的,任何一方修改都会同步
<div id="app">
<my-component></my-component>
<my-component></my-component>
<my-component></my-component>
</div>
var data = {
counter:0
};
Vue.component('my-component',{
template:'<button @click="counter++">{{counter}}</button>',
data(){
return data
}
});
var app = new Vue({
el:'#app',
})
所以解决方法就是给组件返回一个新的data对象来独立,3个按钮就不会互相影响了
<div id="app">
<my-component></my-component>
<my-component></my-component>
<my-component></my-component>
</div>
Vue.component('my-component',{
template:'<button @click="counter++">{{counter}}</button>',
data(){
return {
counter:0
}
}
});
var app = new Vue({
el:'#app',
})
2.使用props传递数据
(1)父组件向子组件正向传递参数或数据通过props来实现,props值有两种,一种是字符串数组,一种是对象;
props数据和组件data里return出的数据的区别是:props的数据来自父级,data中的数据是组件自己的数据,作用域在组件本身
(2)如果子组件接收的是父组件的动态数据,就可以用v-bind动态绑定props的值:
<input type="text" v-model="parentMessage">
<my-component :message="parentMessage"></my-component>
Vue.component('my-component',{
props:['message'],
template:'<div>{{message}}</div>'
});
var app = new Vue({
el:'#app',
data:{
parentMessage:''
}
})
(3)单向数据流
单向数据流即父组件变化时会传递给子组件,但反过来不可以;
业务中会经常遇到两种需要改变props的情况:
第一种:父组件传初始值进来,子组件将其作为初始值保存起来,在自己作用域下随意使用和修改
这种情况可以在组件data内再声明一个数据,引用父组件的prop:
<my-component :init-count="1"></my-component>
Vue.component('my-component',{
props:['initCount'],
template:'<div>{{count+1}}</div>',
data(){
return{
count:this.initCount
}
}
});
var app = new Vue({
el:'#app'
})
组件中声明了数据count,它在组件初始化时会获取来自父组件的initCount,之后就与之无关了,只用维护count,可以避免直接操作initCount.
第二种:prop作为需要被转变的原始值传入,可用计算属性
<my-component :width="100"></my-component>
Vue.component('my-component',{
props:['width'],
template:'<div :style="style">组件内容</div>',
computed:{
style(){
return{
width:this.width + 'px'
}
}
}
});
var app = new Vue({
el:'#app'
})
(4)数据验证
当prop需要验证时就需要对象写法:
Vue.component('my-component',{
props:{
//必须是数字类型
propA:Number,
// 必须是字符串或数字类型
propB:[String,Number],
// 布尔值,如果没有定义,默认为true
propC:{
type:Boolean,
default:true
},
// 数字,必传
propD:{
type:Number,
required:true
},
// 若是数组或对象,默认值必须函数返回
propE:{
type:Array,
default:function(){
return []
}
},
//自定义一个验证函数
propF:{
validator(value){
return value > 10
}
}
}
});
验证的type类型可以是:
String、number、boolean、object、array、function
3.组件通信
(1)自定义事件
当子组件向父组件传递数据时,会用到自定义事件:
子组件用$emit()来触发事件,父组件用$on()来监听子组件的事件(也可在子组件自定义标签上通过v-on来监听)
<p>总数:{{total}}</p>
<my-component @increase="handleGetTotal" @reduce="handleGetTotal"></my-component>
Vue.component('my-component',{
template:`
<div>
<button @click="handleIncrease">+1</button>
<button @click="handleReduce">-1</button>
</div>`,
data(){
return{
counter:0
}
},
methods:{
handleIncrease(){
this.counter++
this.$emit('increase',this.counter)
},
handleIncrease(){
this.counter--
this.$emit('reduce',this.counter)
}
}
});
var app = new Vue({
el:'#app',
data:{
total:0
},
methods:{
handleGetTotal(total){
this.total = total
}
}
})
(2)使用v-model
用来创建自定义的表单输入组件,进行数据双向绑定:
<p>总数:{{total}}</p>
<my-component v-model="total"></my-component>
<button @click="handleReduce">-1</button>
Vue.component('my-component',{
props:['value'],
template:'<input :value="value" @input="updateValue">',
methods:{
updateValue(event){
this.$emit('input',event.target.value)
}
}
});
var app = new Vue({
el:'#app',
data:{
total:0
},
methods:{
handleReduce(){
this.total--
}
}
})
实现这样一个具有双向绑定的v-model组件要满足以下两个要求:
~接收一个value属性
~在有新的value时触发input事件
(3)非父子组件通信
使用一个空的vue实例作为中央事件总线bus,即一中介。
<div id="app">
{{message}}
<my-component></my-component>
</div>
var bus = new Vue() // 中央事件总线bus
Vue.component('my-component',{
template:'<button @click="handleEvent">传递事件</button>',
methods:{
handleEvent(){
bus.$emit('on-message','来自组件my-component的内容')
}
}
});
var app = new Vue({
el:'#app',
data:{
message:''
},
mounted(){
var _this = this
// 在实例初始化时 监听来自bus实例的事件
bus.$on('on-message',(msg) => {
_this.message = msg
})
}
})
(4)父链和子组件索引
父链:
在子组件中,使用this.$parent可以直接访问该组件的父实例或组件,父组件可以通过this.$children访问它所有的子组件;
而且可以递归向上或向下无限访问,直到根实例或最内层的组件。
{{message}}
<my-component></my-component>
Vue.component('my-component',{
template:'<button @click="handleEvent">通过父链直接修改数据</button>',
methods:{
handleEvent(){
// 访问到父链后,直接修改数据
this.$parent.message = '来自组件my-component的内容'
}
}
});
var app = new Vue({
el:'#app',
data:{
message:''
}
})
子组件索引:
用属性ref为子组件指定一个索引名称
<button @click="handleRef">通过ref获取子组件实例</button>
<my-component ref="comA"></my-component>
Vue.component('my-component',{
template:'<div>子组件</div>',
data(){
return{
message:'子组件内容'
}
}
});
var app = new Vue({
el:'#app',
methods:{
handleRef(){
// 通过$ref来访问指定的实例
var msg = this.$refs.comA.message
console.log(msg)
}
}
})
4.使用slot分发内容
props传递数据、events触发事件、slot内容分发构成了vue组件的3个API来源
slot分发的内容,作用域是在父组件上的
(1)单个slot
在子组件内使用slot元素为子组件开启slot插槽,在父组件模板里,插入在子组件标签内的所有内容将替代子组件的<slot>标签及它的内容
<child-component>
<p>分发的内容</p>
<p>更多分发的内容</p>
</child-component>
Vue.component('child-component',{
template:`<div>
<slot>
<p>如果父组件没有插入内容,我将作为默认内容出现</p>
</slot>
</div>`
});
var app = new Vue({
el:'#app'
})
子组件<slot>内的备用内容,作用域是子组件本身
(2)具名slot
给<slot>元素指定一个name后可以分发多个内容
<child-component>
<h2 slot="header">标题</h2>
<p>正文内容</p>
<p>更多正文内容</p>
<div slot="footer">底部信息</div>
</child-component>
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'
})
(3)作用域插槽
使用一个可复用的模板替换已渲染元素
<child-component>
<template scope="props">
<p>来自父组件的内容</p>
<p>{{props.msg}}</p>
</template>
</child-component>
Vue.component('child-component',{
template:`
<div class="container">
<slot msg="来自子组件的内容"></slot>
</div>
`
});
var app = new Vue({
el:'#app'
})
作用域插槽更具代表性的用例是列表组件,允许组件自定义应该如何渲染列表每一项
<!-- :books="books"是父组件传给子组件的数据 -->
<my-list :books="books">
<!-- 作用域插槽也可以是具名的slot -->
<template slot="book" scope="props">
<li>{{props.bookName}}</li>
</template>
</my-list>
Vue.component('my-list',{
props:{
books:{
type:Array,
default(){
return []
}
}
},
template:`
<ul>
<slot name="book" v-for="book in books" :book-name="book.name"></slot>
</ul>
`
});
var app = new Vue({
el:'#app',
data:{
books:[
{name:'《vue.js实战》'},
{name:'《vue.js实战2》'},
{name:'《vue.js实战3》'}
]
}
})
通过$slots可以访问某个具名slot,this.$slots.default包括了所有没有被包含在具名slot中的节点。
5.组件高级用法
(1)递归组件
给组件设置name,组件可以在它的模板内自己调用自己;但必须给一个条件来限制递归数量,否则会抛出错误
<child-component :count="1"></child-component>
Vue.component('child-component',{
name:'child-component',
props:{
count:{
type:Number,
default:1
}
},
template:`
<div class="child">
<child-component :count="count+1" v-if="count<3"></child-component>
</div>
`
});
var app = new Vue({
el:'#app'
})
(2)内联模板
使用组件时,给组件标签使用inline-template特性,组件就会把它的内容当做模板,而不是当内容分发。
但缺点就是作用域比较难以理解,所以不要轻易使用内联模板。
(3)动态组件
使用元素<component>及其is特性动态挂载不同的组件
<component :is="currentView"></component>
<button @click="handleChangeView('A')">切换到A</button>
<button @click="handleChangeView('B')">切换到B</button>
<button @click="handleChangeView('C')">切换到C</button>
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(com){
this.currentView = 'com' + com
}
}
})
(4)异步组件
vue.js只在组件需要渲染时触发工厂函数,并把结果缓存起来,用于后面的再次渲染。
(5)$nextTick
this.$nextTick()将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。
它跟全局方法 Vue.nextTick 一样,不同的是回调的 this 自动绑定到调用它的实例上。
(6)手动挂载实例
Vue.extend和$mount 这两个方法可以手动挂载一个实例
Vue.extend是基础vue构造器,创建一个子类,参数是一个包含组件选项的对象
如果vue实例在实例化时没有el选项,就处于未挂载状态,没有关联的DOM元素,可以使用$mount()手动挂载一个未挂载的实例。
<div id="mount-div">
</div>
var myComponent = Vue.extend({
template:'<div>Hello {{name}}</div>',
data(){
return{
name:'lyf'
}
}
})
new myComponent().$mount('#mount-div')