假设想在表格便签内使用一个组件作为行,直接使用组件是不行的,原因在于tbody内只能放tr便签,否则浏览器就会将tbody内的标签解析到外面,从而导致结构的错乱。
为了解决这个问题,我们可以使用is特性,将is特性设置为组件名称,这样既保证了tbody和tr之间的层级关系,也使用了组件。
一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝。
在Vue中,可以为DOM节点设置ref属性,从而可以通过实例名(实例引用this).$ref.属性名
来访问DOM节点。
Prop 是你可以在组件上注册的一些自定义 attribute。当一个值传递给一个 prop attribute 的时候,它就变成了那个组件实例的一个 property。
<body>
<div id="root">
<counter :count="count1"></counter>
<counter :count="count2"></counter>
</div>
<script>
var counter = {
props:['count'],
data:function(){
// 子组件不能直接修改父组件传过来的值,因此需要将附件传过来的值复制一份
return {
number:this.count
}
},
template:'{{number}}',
methods:{
handleClick:function(){
this.number ++;
}
}
}
var vm = new Vue({
el:"#root",
data:{
count1:0,
count2:1
},
components:{
counter:counter
}
})
</script>
</body>
<body>
<div id="root">
<counter :count="count1" @change="handleChange"></counter>
<counter :count="count2" @change="handleChange"></counter>
<div>{{total}}</div>
</div>
<script>
var counter = {
props:['count'],
data:function(){
// 子组件不能直接修改父组件传过来的值,因此需要将附件传过来的值复制一份
return {
number:this.count
}
},
template:'{{number}}',
methods:{
handleClick:function(){
this.number ++;
this.$emit('change',1);
}
}
}
var vm = new Vue({
el:"#root",
data:{
count1:0,
count2:1,
total:1
},
components:{
counter:counter
},
methods:{
handleChange:function(step){
this.total += step;
}
}
})
</script>
</body>
</html>
通过在子组件中触发自定义事件$emit('事件名',参数列表...)
,父组件可以监听到这个事件。
== 一句话总结 ==:父组件通过props给子组件传值,子组件通过事件触发给父组件传值。
假设我们在子组件中限定,父组件必须传一个字符串给子组件。
<body>
<div id="root">
<child :content="content"></child>
</div>
<script>
// 全局子组件
Vue.component('child',{
props:{
content:String
},
template:'{{content}}'
})
var vm = new Vue({
el:'#root',
data:{
content:123
}
})
</script>
</body>
当父组件传了一个数字后,Vue会警告如下:
当允许多个类型时,使用数组语法:
props:{
content:[Number,String],//数组语法表示,该参数既可以接受Number类型也可以接受String类型
}
更详细的参数校验:
<body>
<div id="root">
<child :content="content"></child>
</div>
<script>
Vue.component('child',{
props:{
content:{
type:String,
required:true,
default:'default value',
// 校验长度
validator:function(value){
return (value.length>5)
}
}
},
template:'{{content}}'
})
var vm = new Vue({
el:'#root',
data:{
content:"Hello,world"
}
})
</script>
</body>
若父组件向子组件传递了一个参数content
,但子组件并未接受(即子组件的props
属性内不包含content
)时,父组件的content
特性成为非props特性,当然在子组件中无法获取该特性的值。非props属性会显示在DOM属性中,而props属性不会。
当我们想给子组件上绑定原生事件,例如click,我们首先可能会想到这么做:
<body>
<div id="root">
<child @click="handleClick"></child>
</div>
<script>
Vue.component('child',{
template:'Hello,world'
})
var vm = new Vue({
el:'#root',
methods:{
handleClick:function(){
alert("click");
}
}
})
</script>
</body>
这样做的结果是:无论点击多少次,都无法触发click事件处理函数。
原因在于,直接给子组件的DOM元素上绑定的事件全部为自定义事件,click也被识别为自定义事件,我们没有关于click的触发定义,因此不会触发handleClick。
那么怎么给子组件绑定原生事件呢?
我们需要把事件绑定到子组件的模板上,并且相关的事件处理函数也应该定义在子组件内。
<body>
<div id="root">
<child></child>
</div>
<script>
Vue.component('child',{
template:'Hello,world',
methods:{
handleClick:function(){
alert("click");
}
}
})
var vm = new Vue({
el:'#root',
})
</script>
</body>
要想触发原生事件,可以在子组件的原生事件处理函数中使用$emit('click')
<body>
<div id="root">
<child @click="handleClick"></child>
</div>
<script>
Vue.component('child',{
template:'Hello,world',
methods:{
handleChildClick:function(){
alert("click child");
this.$emit('click');
}
}
})
var vm = new Vue({
el:'#root',
methods:{
handleClick:function(){
alert("click");
}
}
})
</script>
</body>
以上的做法过于繁琐,Vuet提供了组件修饰符:
<child @click.native="handleClick"></child>
@事件名.native
表明该事件是一个原生事件
方式一:使用Vuex
方式二:使用发布订阅模式(总线机制/BUS/发布订阅模/观察者模式)
下面介绍方式二:
Vue.prototype.bus = new Vue();
在Vue类的prototype上挂了一个bus属性,该属性是Vue类型的对象。因为是在原型上,Vue类的每个实例都会有一个bus属性,而且指向同一个Vue实例。
我们可以通过bus属性来统一地触发事件并携带参数。
<body>
<div id="root">
<child content="dell"></child>
<child content="lee"></child>
</div>
<script>
Vue.prototype.bus = new Vue();
// 在Vue类的prototype上挂了一个bus属性,该属性是Vue类型的对象
// 因为是在原型上,Vue类的每个实例都会有一个bus属性,而且指向同一个Vue实例
Vue.component('child',{
data:function(){
return {
selfContent:this.content
}
},
props:{
content:String
},
template:'{{selfContent}}',
methods:{
handleClick:function(){
this.bus.$emit('change',this.selfContent);
}
},
mounted:function(){
var that = this;
this.bus.$on('change',function(msg){
that.selfContent = msg;
})
}
})
var vm = new Vue({
el:"#root"
})
</script>
</body>
使用场景:当子组件中有一部分DOM元素时根据父组件传递过来的值进行渲染的,这时使用props传值会直接将HTML标签渲染在页面上,为了避免这一错误,我们可能需要使用div来包裹,并且使用v-html指令来确保其格式正确,如下:
<body>
<div id="root">
<child content='World
'></child>
</div>
<script>
Vue.component('child',{
props:['content'],
template:`
Hello
`
})
var vm = new Vue({
el:"#root"
})
</script>
</body>
这样的方式在传递的代码量大的情况下会非常糟糕。
这种情况下,可以使用插槽。
<body>
<div id="root">
<child>
<h2>World</h2>
</child>
</div>
<script>
Vue.component('child',{
props:['content'],
template:`
Hello
`
})
var vm = new Vue({
el:"#root"
})
</script>
</body>
在这个例子中,我们将需要插入到子组件的内容,直接以HTML标签的形式写在了子组件的占位标签内
,在子组件的模板中使用
标签可以接收到自己占位标签内的所有内容。
可以在插槽内指定默认内容,该内容会在父组件未给子组件的插槽传递值时显示,若传递了,则默认隐藏。
<slot>默认内容</slot>
具名插槽
为了在子组件中使用多个插槽,而每个插槽拥有不同的内容,我们需要给插槽起名:在外部,给传进插槽的DOM元素指定slot特性;在内部,给插槽指定name特性,与外部要传入该插槽的slot特性名一致。
<body>
<div id="root">
<body-content>
<div class='header' slot='header'>header</div>
<div class='footer' slot='footer'>footer</div>
</body-content>
</div>
<script>
Vue.component('body-content',{
props:['content'],
template:`
content
`
})
var vm = new Vue({
el:"#root"
})
</script>
</body>
子组件在渲染数据时,可以不指定具体的渲染样式,而由父组件来决定该数据渲染成什么样子。
做法:
在子组件的模板中定义插槽:
template:`
`
在父组件对应子组件的位置使用标签声明作用域插槽,并使用
slot-scope
属性接受参数。
作用域插槽必须包裹在template标签内
<template slot-scope="props">
<li>{{props.item}}</li>
</template>
<body>
<div id="root">
<child>
<template slot-scope="props">
<li>{{props.item}}</li>
</template>
</child>
</div>
<script>
Vue.component('child',{
data:function(){
return{
list:[1,2,3,4]
}
},
template:`
`
})
var vm = new Vue({
el:"#root"
})
</script>
</body>
如果我们想根据一些条件动态地决定渲染哪些组件,不渲染哪些组件,我们可以这样做:
<body>
<div id="app">
<child-one v-if="type === 'child-one'"></child-one>
<child-two v-if="type === 'child-two'"></child-two>
<button @click="handleSwtich">切换</button>
</div>
<script>
Vue.component('child-one',{
template:'child-one'
})
Vue.component('child-two',{
template:'child-two'
})
var vm = new Vue({
el:"#app",
data:{
type:'child-one'
},
methods:{
handleSwtich:function(){
this.type = this.type === 'child-one' ? 'child-two' : 'child-one';
}
}
})
</script>
</body>
以v-if的方式切换组件的渲染与否,每次切换都会有一个组件被销毁,有一个组件被渲染,若每次销毁和新建的组件内容一样,则会非常浪费性能。
为解决这个问题,可以使用v-once
指令,使用了v-once
指令的组件,会在第一次渲染时就被加载进内存,以后使用v-if切换时不会销毁和重新创建,而是直接从内存中加载,这样做能提高性能。
Vue提供了动态组件,可以更好地解决这个问题:
<component :is="type"></component>
所代表的组件为动态组件,它的is
特性指明它的类型。通过动态地改变is
特性达到动态改变组件类型的目的。
Vue.component('child-one',{
template:'child-one'
})
Vue.component('child-two',{
template:'child-two'
})