- A+
所属分类:Web前端
一.文章导读
前面学习了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 { width: 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" alt="vue组件学习" 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: { width: '100%', height: '400px' }, // 上一页 prepagestye: { width: '35px', position: 'absolute', top: '190px', display: 'inline-block', left: '8px', cursor: 'pointer', }, // 下一页 nextpagestye: { width: '35px', position: 'absolute', top: '190px', right: '8px', cursor: 'pointer', display: 'inline-block', transform: ' rotate(180deg)', }, slideshowstyle: { position: 'relative', width: '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; width: 100%; height: auto; width: 30px; height: 30px; transform: rotate(180deg); cursor: pointer; display: inline-block; } </style> --> </body> </html>