和搭积木,分类一样,把功能模块划分开。先搭好一个一个组件,然后合起来。
组件的基本使用方法
全局组件
创建构造器和注册组件要放在new Vue()之前
<div id="app1">
<!-- 3.使用注册的组件 -->
<X></X>
<X></X>
</div>
<script>
//1.创建组件构造器
//extend方法传入一个对象
const cpnC = Vue.extend({
template:
`<div>
<h2>这是组件的标题</h2>
<p>这是组件的一段文字</p>
</div>`
});
//2.注册组件(全局组件,意味着可以在多个Vue实例下使用)
//两个参数,①标签名,你想要什么样的标签使用这个组件。②上面组成主键的cpnC
Vue.component("X",cpnC);
const app =new Vue({
el:"#app1",
data:{
massage:" 你好",
age:0
}
})
</script>
局部组件
<div id="app1">
<!-- 3.使用注册的组件 -->
<X></X>
<X></X>
</div>
<script>
//1.创建组件构造器
//extend方法传入一个对象
const cpnC = Vue.extend({
template:
`<div>
<h2>这是组件的标题</h2>
<p>这是组件的一段文字</p>
</div>`
});
const app =new Vue({
el:"#app1",
data:{
massage:" 你好",
age:0
},
components:{ //2.局部组件,X是组件名,cpnC就是上面的组件构建器
X:cpnC
}
})
</script>
全局组件可以在多个Vue实例中使用,而局部组件只能在某个Vue实例中使用
语法糖简便写法
<div id="app1">
<!-- 使用注册的组件 -->
<X></X>
<X2></X2>
</div>
<script>
//简便注册全局组件
Vue.component("X",{
template:
`<div>
<h2>组件标题</h2>
<p>一行字</p>
</div>`
});
//Vue实例就是个root组件
const app =new Vue({
el:"#app1",
data:{
massage:" 你好",
age:0
},
components:{
X2:{template:`<div><h2>组件标题</h2><p>一行字</p></div>`}//局部组件
}
})
</script>
抽离模板写法
抽离模板一定要写一个大的标签,包裹子组件里面的全部标签。template里面只能有一层子标签
<div id="app1">
<!-- 使用注册的组件 -->
<X></X>
<X2></X2>
</div>
<!-- 第一种抽离模板写法 -->
<script type="text/x-template" id="cpn1">
<div>
<h2>抽离模板写法1</h2>
</div>
</script>
<!-- 第二种抽离模板写法 -->
<template id="cpn2">
<div>
<h2>抽离模板写法2</h2>
</div>
</template>
<script>
//简便注册全局组件
Vue.component("X",{
template:"#cpn1"
});
Vue.component("X2",{
template:"#cpn2"
});
const app =new Vue({
el:"#app1",
data:{
},
components:{
}
})
</script>
父子组件
注册使用父子组件
<div id="app1">
<!-- 使用注册的组件 -->
<cpn2></cpn2>
<!-- 在root组件里面没有注册cpn1组件,所以不能在这里使用,只能在注册它的地方使用 -->
<!-- 子组件可以同时在多个父组件中注册 -->
</div>
<script>
const cpnC1 = Vue.extend({
template:
`<div>
<h2>我是子组件</h2>
<p>这是组件的一段文字</p>
</div>`
});
const cpnC2 = Vue.extend({
template:
`<div>
<h2>我是父组件</h2>
<p>下一行就是我的子组件cpn1</p>
<cpn1></cpn1>
</div>`,
components:{
cpn1:cpnC1 //在第二组件里面注册组件
}
});
//Vue实例就是个root组件
const app =new Vue({
el:"#app1",
data:{
massage:" 你好",
age:0
},
components:{
cpn2:cpnC2 //cpnC1在cpnC2里面注册了,但是cpnC2作为父组件还没注册,注册cpnC2的局部组件
}
})
</script>
组件数据
Vue实例有一个data,如果所有的数据全部放在Vue实例的data里面,将会非常臃肿。每个组件都有自己保存数据的地方
组件的data数据必须是一个方法,不是像Vue实例那样是一个对象。这个方法里面返回一个对象,这个返回的对象里面写数据。 为什么组件里面的data必须是一个方法? 组件是经常复用的,一个组件用了多次,如果data是一个对象的话,这些复用的组件用的是同一个data对象,数据共用了。组件里面是方法,每次复用都会调用data方法,返回一个新的对象,所以不会造成数据复用
<div id="app1">
<!-- 使用注册的组件 -->
<X></X>
<X2></X2>
</div>
<template id="cpn1">
<div>
<h2>{{title}}</h2>
</div>
</template>
<script>
Vue.component("X",{
template:"#cpn1",
data(){ //组件里面的data不能是一个对象,必须是一个方法,方法返回一个对象
return {
title:"这是标题"
}
}
});
const app =new Vue({
el:"#app1",
data:{
},
components:{
}
})
</script>
父子组件通信
每个组件里面都有生命周期,数据,方法等。父组件里面包含了很多子组件。一层套一层。但是执行网络请求,向服务器要数据的通常是最外面的一层组件。这种时候就需要数据在父子组件间传来传去了。
父传子
子组件要接受父组件传来到的数据,子组件里面选项props的值是可以是一个数组也可以是对象。
相当于给这个子组件设置属性,在Vue实例,el挂载的范围内,使用这个子组件,用v-ind绑定子组件自定义的props属性,绑定Vue实例里面的data数据。
在子组件里面就可以调用这个属性值。
如果不用v-bind,就相当于传入字符串而已。如果props对象值里面设置了默认值,v-bind没有传参,就使用默认值
props属性命名尽量不要用驼峰标识,因为html页面不区分大小写,在props里面用了驼峰命名,v-bind里面哪怕你写了驼峰也会被识别成小写,v-bind和props就不一致,传不过来数据
<div id="app1">
<!-- 使用注册的组件 -->
<cpn1 :ctext="text" :clist="list"></cpn1>
</div>
<template id="cpn1">
<div>
<h2>{{ctext}}</h2>
<ul>
<li v-for="item in clist">{{item}}</li>
</ul>
</div>
</template>
<script>
const cpn1={
template:"#cpn1",
data(){
return {
title:"这是标题"
}
},
//props:['ctext',"clist"] //数组形式
//还可以是对象形式,可以直接 限定传入的数据类型,值也可以是对象
props:{
ctext:{
type:String,
default:'默认值',
required:true //毕传值,不传就报错
},
clist:Array,
clist2:{
type:Array,
default(){ //类型是对象或者数组时,默认值必须是一个方法
return []
}
}
}
}
const app =new Vue({
el:"#app1",
data:{
text:"这是父组件的一段文字",
list:["海贼王","火影忍者","名侦探柯南"],
list2:["a","b","c","d"]
},
components:{
cpn1 //增强字面量写法,直接把上面定义好的cpn1写到这里。cpn1就是组件名,cpn1里面的对象就是值
}
})
</script>
子传父
以下代码,在子组件的button点击过后,执行了子组件里面的方法btnclick,在这个子组件的方法里面,用this指向当前的子组件,定义了3个事件。第一个参数为事件名,第二个参数为需要传递的数据。
在父组件里面使用这个子组件,然后v-on监听这个子组件cpn1独有的自定义事件,和v-on监听click等默认事件一样的用法。绑定到父组件的方法。
如果子事件传过来的有数据,父组件方法里面写上形参,并不用传实参,因为会像默认事件一样。默认事件不写实参就会传入event事件对象,子组件的自定义事件没有event事件对象,所以默认传入的就是自定义事件的参数。如果要传入多个参数,自定义时写数组,对象。
父组件传给子组件数据经过子组件加工后,又传回给父组件,同理,看代码有写
<div id="app1">
<!-- 使用注册的组件 -->
<cpn1 :cmassage="massage" @new_event="vue_method" @new_event2="vue_method2" @new_event3="vue_method3"></cpn1>
</div>
<template id="cpn1">
<div>
<input type="text" v-model="text" placeholder="子组件自带数据"/>
<input type="text" v-model="cmassage" placeholder="父组件传过来的数据"/>
<button @click="btnclick">点击发送给父组件</button>
</div>
</template>
<script>
const cpn1={
template:"#cpn1",
data(){
return {
text:"这是文字"
}
},
props:{
cmassage:{
type:String,
}
},
methods:{
btnclick(){
//自定义事件
this.$emit('new_event');
this.$emit('new_event2',this.text)
this.$emit('new_event3',[this.text,this.cmassage])//多个参数用数据,对象
}
}
}
const app =new Vue({
el:"#app1",
data:{
massage:"父组件的massage"
},
components:{
cpn1
},
methods:{
vue_method(){
console.log("点击子组件的按钮,传到了父组件这里");
},
vue_method2(text){
console.log("这个父组件第二个方法,传过来的参数:"+text);
},
vue_method3(datas){
console.log("传过来子组件本身自带的数据:"+datas[0]+",父组件传给子组件加工后又传给父组件的数据:"+datas[1]);
}
}
})
</script>
父访问子
在父组件里面用$children或者$refs就能访问到子组件的属性方法了
- $children能拿到在当前组件下的全部使用的子组件,并不是全部注册的,有时候一个组件重复使用。通过索引来拿,但是如果中途加进入几个子组件,索引就乱了。通常不使用这个方法访问子组件
- $refs是一个对象类型,默认为空。需要在使用子组件时给子组件加上ref属性,给属性赋值一个名字。就能通过refs.名字 来访问到对应的子组件,一般用这个方法访问子组件
```html
这是子组件
<a name="ESOlD"></a>
## 子访问父
在子组件里面可以通过**$parent**来访问他的父组件,但是不能访问爷爷组件,而且不建议这么写,子组件本来就是抽出来的模块,需要复用。子组件如果知道了父组件的相关特性,那就依赖死了,组件之间需要独立性。
```html
<div id="app1">
<cpn1></cpn1>
</div>
<template id="cpn1">
<div>
<h2>这是第二层组件</h2>
<cpn2></cpn2>
</div>
</template>
<template id="cpn2">
<div>
<h2>这是最里面的一层组件</h2>
<button @click="btnclick">按钮</button>
</div>
</template>
<script>
const cpn2={
template:"#cpn2",
data(){
return{
name:"李四"
}
},
methods:{
btnclick(){
//访问父组件,不能访问爷爷组件
console.log(this.$parent);
console.log(this.$parent.name);
//访问根组件
console.log(this.$root.massage);
}
}
}
const cpn1={
template:"#cpn1",
data(){
return {
name:"张三"
}
},
methods:{
},
components:{
cpn2
}
}
const app =new Vue({
el:"#app1",
data:{
massage:"一条消息"
},
components:{
cpn1
},
methods:{
}
})
</script>
slot插槽(vue2.6以上用v-slot)
生活中,比如电脑上就有很多插槽,usb插槽好几个,插上鼠标,键盘,数位板,就相当于给原来的电脑扩展了一些功能。组件也一样,让其更加具有拓展性。抽取共性,保留不通。一样的地方封装在组件里面,复用时可能会不一样的地方设置个slot插槽,需要什么东西使用组件时插进入
在定义子组件模板时,考虑到组件复用时可能不一样的地方,用
使用组件时,在组件标签中间插入需要插入的东西,会自动填充到slot里面
单个slot
<div id="app1">
<cpn1><button>按钮</button></cpn1>
<cpn1></cpn1>
<cpn1></cpn1>
</div>
<template id="cpn1">
<div>
<h2>这是子组件</h2>
<input type="text" />
<slot></slot>
</div>
</template>
<script>
const cpn1={
template:"#cpn1",
data(){
return {
name:"张三",
age:30
}
},
methods:{
}
}
const app =new Vue({
el:"#app1",
data:{
massage:"一条消息"
},
components:{
cpn1
},
methods:{
}
})
</script>
默认slot
可以在slot里面设置一个默认的内容,如果使用组件时,没有插入内容,就使用默认的
<div id="app1">
<cpn1><button>非默认</button></cpn1>
<cpn1></cpn1>
<cpn1></cpn1>
</div>
<template id="cpn1">
<div>
<h2>这是子组件</h2>
<input type="text" />
<slot><button>默认按钮</button></slot>
</div>
</template>
多个slot
如果多个slot,使用组件时只插入一个,会替换掉全部slot
<div id="app1">
<cpn1><h1>新内容</h1></cpn1>
<cpn1></cpn1>
<cpn1></cpn1>
</div>
<template id="cpn1">
<div>
<slot><h2>左边</h2></slot>
<slot><h2>中间</h2></slot>
<slot><h2>右边</h2></slot>
</div>
</template>
v-slot
一般使用插槽都使用v-slot,上面的是老版本的写法,v-slot在vue2.6之后版本才有。 有时候在定义组件模板时,需要传入数据,因为
多个插槽,一般都要给
在使用组件时,在组件中间加入模板标签,写上v-slot,冒号后面就是你要替换的插槽名。
在slot标签上写上自定义的属性。在使用这个组件时,可以给v-slot付一个值,这个值得名字随便取,这个值就是替换的插槽的自定义属性的集合。也就是说:aa="name" :bb="age" cc="这是字符串"
会被当做X的属性来用<div id="app1">
<cpn1>
<template v-slot:a>
<p>可以这样写,需要vue2.6以上的版本</p>
<p>这个template标签替换名为a的slot</p>
</template>
<!-- 这种写法过时了 -->
<input type="text" slot="b" />
<template v-slot:c="X">
<h2>{{X.aa}}</h2>
<h2>{{X.bb}}</h2>
<h2>{{X.cc}}</h2>
</template>
</cpn1>
<cpn1></cpn1>
<cpn1></cpn1>
</div>
<template id="cpn1">
<div>
<slot name="a"><h2>左边</h2></slot>
<slot name="b"><h2>中间</h2></slot>
<slot name="c" :aa="name" :bb="age" cc="这是字符串"><h2>右边</h2></slot>
</div>
</template>