一.文章导读
前面学习了vue的基础指令,了解了相关的钩子函数,这一章学习vue的组件,组件 (Component) 是 Vue.js 最强大的功能之一,组件可以扩展 HTML 元素,封装可重用的代码,组件系统让我们可以用独立可复用的小组件来构建大型应用,几乎任意类型的应用的界面都可以抽象为一个组件树:
菜鸟教程地址:https://www.runoob.com/vue2/vue-tutorial.html
官网学习地址:https://cn.vuejs.org/
入门学习更加推荐菜鸟
二.组件定义
1.全局组件
<body>
<!-- 创建两个承载容器 -->
<div id="app">
<my-fristcomponent></my-fristcomponent>
</div>
<div id="app1">
<!-- 在视图层 直接写组件的标签名就可以调用全局组件 -->
<my-fristcomponent></my-fristcomponent>
</div>
<!--
值得注意的点是 在js中定义了Vue全局组件在视图层中我们的全局组件必须在创建实例的块中使用
-->
<!-- 如下,app2我们没有绑定vue实例所以组件不会生效-->
<div id="app2">
<my-fristcomponent></my-fristcomponent>
</div>
<my-fristcomponent></my-fristcomponent>
<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
/*
自定义全局组件 my-fristcomponent是自定义组件的标签名
template是模板的意思,后面写组件的内容
*/
Vue.component('my-fristcomponent', {
template: "<p>我的第一个组件</p>"
});
/*
创建 两个vue实例
*/
new Vue({
el: "#app"
});
new Vue({
el: "#app1"
})
</script>
</body>
2.组件驼峰命名
<body>
<div id="app">
<!--
在使用组件,如果使用驼峰命名,vue会自动解析,我们
调用时需要用-隔开并使用小写,如下两种方式是可以
-->
<Hello-world></Hello-world>
<hello-world></hello-world>
<!-- 错误 -->
<HelloWorld></HelloWorld>
</div>
<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
/* 在创建实例时,我们遵循驼峰命名法 */
Vue.component('HelloWorld', {
template: "<p>HelloWorld</p>"
})
new Vue({
el: "#app",
})
</script>
</body>
3.组件data数据
<body>
<div id="app">
<data-component></data-component>
<button @click="change">改变</button>
{{msg}}
</div>
<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
Vue.component("dataComponent", {
/*
在定义组件时也可以写data,但是data里面必须是一个函数
且这个函数必须有返回值
*/
data: function() {
return {
msg: "data返回值"
}
},
// 组件中的msg 只可以在该模板中使用
template: '<p >{{msg}}</p>'
})
new Vue({
el: "#app",
data: {
msg: "121"
},
methods: {
// 定义change返回无法改变组件中的
change: function() {
// alert(11);
this.msg = "1212";
}
}
})
</script>
</body>
4.局部组件
前面已经写个全局指令,全局过滤器,局部过滤器之类的,所以局部组件与全局组件的区别是一样的,局部组件只能在当前实例才能使用
<body>
<div id="app">
<my-component></my-component>
</div>
<div id="app1">
// my-component组件注册不正确 局部组件不能在其他实例中使用
<my-component></my-component>
</div>
<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
var vm = new Vue({
el: "#app",
/* data: {
msg: "局部指令",
}, */
components:{
'my-component':{
data:function(){
return{
msg:"局部指令"
}
},
template:"<p>{{msg}}</p>"
}
}
});
new Vue({
el:"#app1"
})
</script>
</body>
注意:组件模板中的内容只允许存在一个根标签,多了的话只会解析第一个
// 下面模板定义了两个p标签,相当于两个根标签,那么第二个就不会生效
template: '<p >{{msg}}</p> <p>{{msg}}</p>
三.组件之间的传值
1.父组件向子组件传值
- 父组件发送的形式是以属性的形式绑定值到子组件身上。
- 然后子组件用属性props接收
- 在props中使用驼峰形式,模板中需要使用短横线的形式字符串形式的模板中没有这个限制
<body>
<div id="app">
<p>实例中定义的属性内容是: {{pmsg}}</p>
#### 下面是组件
<!--
:content 等同于v-bind:content
浏览器打印结果:
我是定义的组件内容
我是标题
父组件内容
-->
<!--
组件定义的模板中使用的值带有props的属性,在视图层使用v-bind绑定
父组件的数据从而形成赋值,也可以不绑定直接赋值
eg:<menu-item title="我自动赋值" content="我自动赋值内容"></menu-item>
-->
<menu-item v-bind:title="ptitle" :content="pmsg"></menu-item>
</div>
<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
// menu-item 定义全局组件
Vue.component('menu-item', {
// 组件 用props 属性来接受父组件传递的参数
props: ['title', 'content'],
//组件中 的data必须是函数,且有返回值
data: function() {
return {
msg: "我是定义的组件内容",
title: "我是定义的组件的标题"
}
},
// 定义组件模版
template: '<p><span>{{msg}}</span><br><span>{{title}}</span><br><span>{{content}}</span></p>'
});
new Vue({
el: "#app",
data: {
pmsg: "父组件内容",
ptitle: "我是标题"
}
});
</script>
</body>
2.子组件向父组件传值
- 子组件用
$emit()
触发事件 $emit()
第一个参数为 自定义的事件名称 第二个参数为需要传递的数据- 父组件用v-on 监听子组件的事件
<body>
<div id="app">
<div :style='{fontSize: fontSize + "px"}'>{{pmsg}}</div>
<!-- 2 父组件用v-on 监听子组件的事件
这里 enlarge-text 是从 $emit 中的第一个参数对应 handle 为对应的事件处理函数
-->
<menu-item :parr='parr' @enlarge-text='handle($event)' @shrink-text='shrink($event)'
@gain-num="gainNume($event)"
></menu-item>
</div>
<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
/*
子组件向父组件传值-携带参数
*/
Vue.component('menu-item', {
props: ['parr'],
/* data: function() {
return {
num: 0,
}
}, */
template: `
<div>
<ul>
<li :key='index' v-for='(item,index) in parr'>{{item}}</li>
</ul>
### 1、子组件用$emit()触发事件
### 第一个参数为 自定义的事件名称 第二个参数为需要传递的数据
<br>
<button @click='$emit("gain-num", 0)'>将子组件的值传递到父组件</button>
<button @click='$emit("enlarge-text", 5)'>扩大父组件中字体大小</button>
<button @click='$emit("shrink-text", 5)'>缩小父组件中字体大小</button>
</div>
`
});
var vm = new Vue({
el: '#app',
data: {
pmsg: '父组件中内容',
parr: ['apple', 'orange', 'banana'],
fontSize: 10
},
methods: {
handle: function(val) {
alert(val)
// 扩大字体大小
this.fontSize += val;
},
shrink: function(val) {
// 缩小 字体大小
this.fontSize -= val;
},
gainNume: function(val){
alert(val);
}
}
});
</script>
</body>
3.兄弟组件的传递
- 兄弟之间传递数据需要借助于事件中心,通过事件中心传递数据
- 提供事件中心 var hub = new Vue()
- 传递数据方,通过一个事件触发hub.$emit(方法名,传递的数据)
- 接收数据方,通过mounted(){} 钩子中 触发hub.$on()方法名
- 销毁事件 通过hub.$off()方法名销毁之后无法进行传递数据
简单想象一下兄弟组件,在全局组件中,定义两个全局组件那么这两个组件就是兄弟组件,局部组件也是一样的,在当个实例中可定义多个局部组件组件之间就是兄弟关系
<body>
<div id="app">
<div>父组件</div>
<div>
<button @click='handle'>销毁事件</button>
</div>
<test-tom></test-tom>
<test-jerry></test-jerry>
</div>
<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
/*
兄弟组件之间数据传递
*/
//1、 提供事件中心
var hub = new Vue();
Vue.component('test-tom', {
data: function() {
return {
num: 0
}
},
template: `
<div>
<div>TOM:{{num}}</div>
<div>
<button @click='handle'>点击</button>
</div>
</div>
`,
methods: {
handle: function() {
//2、传递数据方,通过一个事件触发hub.$emit(方法名,传递的数据) 触发兄弟组件的事件
hub.$emit('jerry-event', 2);
}
},
mounted: function() {
// 3、接收数据方,通过mounted(){} 钩子中 触发hub.$on(方法名
hub.$on('tom-event', (val) => {
this.num += val;
});
}
});
Vue.component('test-jerry', {
data: function() {
return {
num: 0
}
},
template: `
<div>
<div>JERRY:{{num}}</div>
<div>
<button @click='handle'>点击</button>
</div>
</div>
`,
methods: {
handle: function() {
//2、传递数据方,通过一个事件触发hub.$emit(方法名,传递的数据) 触发兄弟组件的事件
hub.$emit('tom-event', 1);
}
},
mounted: function() {
// 3、接收数据方,通过mounted(){} 钩子中 触发hub.$on()方法名
hub.$on('jerry-event', (val) => {
this.num += val;
});
}
});
var vm = new Vue({
el: '#app',
data: {
},
methods: {
handle: function() {
//4、销毁事件 通过hub.$off()方法名销毁之后无法进行传递数据
hub.$off('tom-event');
hub.$off('jerry-event');
}
}
});
</script>
</body>
4.自定义轮播组件
四.组件插槽
- 组件的最大特性就是复用性,而用好插槽能大大提高组件的可复用能力
- 插槽标签指令
- 在组件的基础上的一个标签
1.匿名插槽
- 没有被name属性修饰的插槽叫匿名插槽
<body>
<div id="app">
<alert-box></alert-box>
<!-- 插槽相当于一个默认的占位符,如果没有去修饰值修饰它,它会以默认值展现 -->
<alert-box1>{{msg}}</alert-box1>
</div>
<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
Vue.component("alert-box", {
template: `
<div>
###在组件模板中定一个插槽<br>
<slot>我是一个插槽</slot>
</div>
`
})
Vue.component("alert-box1", {
template: `
<div>
###虽然我也是一个插槽但是我被占用了<br>
<slot>我是一个插槽</slot>
</div>
`
})
new Vue({
el: "#app",
data: {
msg:"我要占用插槽"
}
});
</script>
</body>
2.具名插槽
- 被name属性修饰的插槽叫具名插槽
<body>
<div id="app">
<table-list>
<!-- 在模板内分别为两个插槽填充数据 达到表格效果 -->
<template slot='heands'>
<th>ID</th>
<th>名称</th>
</template>
<template slot='tablebody'>
<tr v-for="l in list">
<td>{{l.id}}</td>
<td>{{l.name}}</td>
</tr>
</template>
</table-list>
</div>
<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
// 定义一个 table 组件
Vue.component('table-list', {
// 定义一个模板,定义两个插槽heands 和 tablebody
template: `
<table>
<tr>
<slot name='heands'></slot>
</tr>
<tbody>
<slot name="tablebody"></slot>
</tbody>
</table>
`
})
new Vue({
el: "#app",
data: {
// 准备初始化数据
list: [{
id: 1,
name: '李四'
},
{
id: 2,
name: '张三'
},
{
id: 3,
name: '张飞'
},
]
}
})
</script>
</body>
3.作用域插槽
- 父组件对子组件加工处理
- 既可以复用子组件的slot,又可以使slot内容不一致
<body>
<div id="app">
<!--
1、当我们希望li 的样式由外部使用组件的地方定义,因为可能有多种地方要使用该组件,
但样式希望不一样 这个时候我们需要使用作用域插槽
-->
<fruit-list :list='list'>
<!-- 2、 父组件中使用了<template>元素,而且包含scope="slotProps",
slotProps在这里只是临时变量
--->
<template slot-scope='slotProps'>
<strong v-if='slotProps.info.id==3' class="current">
{{slotProps.info.name}}
</strong>
<span v-else>{{slotProps.info.name}}</span>
</template>
</fruit-list>
</div>
<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
/*
作用域插槽
*/
Vue.component('fruit-list', {
props: ['list'],
/*
3、 在子组件模板中,<slot>元素上有一个类似props传递数据给组件的写法msg="xxx",
插槽可以提供一个默认内容,如果如果父组件没有为这个插槽提供了内容,会显示默认的内容。
如果父组件为这个插槽提供了内容,则默认的内容会被替换掉
*/
template: `
<div>
<li :key='item.id' v-for='item in list'>
<slot :info='item'>{{item.name}}</slot>
</li>
</div>
`
});
var vm = new Vue({
el: '#app',
data: {
list: [{
id: 1,
name: 'apple'
}, {
id: 2,
name: 'orange'
}, {
id: 3,
name: 'banana'
}]
}
});
</script>
</body>
五.习题练习
1.定义轮播组件
<html>
<head>
<meta charset="utf-8">
<title>自定义轮播组件</title>
</head>
<style type="text/css">
* {
padding: 0;
margin: 0;
}
#slideshow {
800px;
height: 400px;
margin: 50px auto;
border: 1px solid black;
position: relative;
}
</style>
<body>
<div id="slideshow">
<slides-show :images="images" :index="index" :img-style="imgStyle" :show="show" :hide="hide" :prepagestye="prepagestye"
:nextpagestye="nextpagestye" :slideshowstyle="slideshowstyle"
@prepage='prepage' @nextpage='nextpage'
>
</slides-show>
</div>
<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
Vue.component("slides-show", {
props: ['images', 'index', 'slideshowstyle', 'imgStyle', 'show', 'hide',
'prepagestye', 'nextpagestye'
],
template: `
<div :style="[slideshowstyle]">
<ul v-for="imgs in images" class="slideTable">
<li v-if="index === imgs.id" :style="[show]">
<img :src="imgs.path" :style="[imgStyle]">
</li>
<li v-else :style="[hide]">
<img :src="imgs.path" >
</li>
</ul>
<span :style="[prepagestye]" @click='$emit("prepage")'>
<img src = "img/上一页.png" width="30px">
</span>
<span :style="[nextpagestye]" @click='$emit("nextpage")'>
<img src = "img/上一页.png" width="30px">
</span>
</div>
`
})
new Vue({
// 绑定父元素
el: "#slideshow",
data: {
// 定义轮播图片数据
index: 1,
images: [{
id: 1,
path: 'img/3Dbg01.jpg'
},
{
id: 2,
path: 'img/3Dbg02.jpg'
},
{
id: 3,
path: 'img/3Dbg03.jpg'
},
{
id: 4,
path: 'img/3Dbg04.jpg'
}
],
// 隐藏显现
hide: {
display: 'none'
},
show: {
display: 'inline'
},
//定义图片大小
imgStyle: {
'100%',
height: '400px'
},
// 上一页
prepagestye: {
'35px',
position: 'absolute',
top: '190px',
display: 'inline-block',
left: '8px',
cursor: 'pointer',
},
// 下一页
nextpagestye: {
'35px',
position: 'absolute',
top: '190px',
right: '8px',
cursor: 'pointer',
display: 'inline-block',
transform: ' rotate(180deg)',
},
slideshowstyle: {
position: 'relative',
'100%',
height: '400px'
}
},
methods: {
prepage: function() {
console.log('11')
if(this.index <= 1){
this.index = this.images.length;
}else{
this.index = this.index-1;
}
},
nextpage: function() {
console.log('2')
if (this.index >= this.images.length) {
this.index = 1;
} else {
this.index = this.index + 1;
}
}
}
})
</script>
<!-- <style type="text/css">
.slideshowStyele {
position: relative;
text-align: center;
background-color: #fff;
list-style: none;
100%;
height: auto;
30px;
height: 30px;
transform: rotate(180deg);
cursor: pointer;
display: inline-block;
}
</style> -->
</body>
</html>