3 - Vue组件系统
概要
vue.js既然是框架,那就不能只是简单的完成数据模板引擎的任务,它还提供了页面布局的功能。本文详细介绍使用vue.js进行页面布局的强大工具,vue.js组件系统。
组件的分类
每一个新功能的诞生,都是为了解决特定问题的。vue中的组件分为两种,全局组件和局部组件。
通常一个应用会以一棵嵌套的组件树的形式来组织, 你可能会有页头、侧边栏、内容区等组件,每个组件又包含了其它的像导航链接、博文之类的组件。
全局组件
全局组件是一个可复用的vue实例,但是不管用不用都会创建,会占用内存,而且是不会被垃圾回收的,所以不是很好。
通过Vue.component()创建一个全局组件之后,我们可以在一个通过 new Vue
创建的 Vue 根实例【挂载的标签中,比如<div id="app"> .... </div>】,把这个组件作为自定义元素来使用。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="../statics/vue.min.js"></script> </head> <body> <div id="app"> <!--第二步,使用--> <global_component></global_component> </div> <script> // 第一步,注册 Vue.component("global_component", { template: ` <div> <h2>Hello Vue</h2> </div> ` }); new Vue({ el: "#app", }); </script> </body> </html>
组件中template中的元素要通过div包含一层,必须外面包含一层!!!
组件中template中的元素要通过div包含一层,必须外面包含一层!!!
组件中template中的元素要通过div包含一层,必须外面包含一层!!!
组件中template中的元素要通过div包含一层,必须外面包含一层!!!
组件中template中的元素要通过div包含一层,必须外面包含一层!!!
let App = {
里面再写子组件
}
然后下面再调用App
这个是编程规范!!!
局部组件
全局注册往往是不够理想的。比如,如果你使用一个像 webpack 这样的构建系统,全局注册所有的组件意味着即便你已经不再使用一个组件了,它仍然会被包含在你最终的构建结果中。这造成了用户下载的 JavaScript 的无谓的增加。
全局组件始终是存在的,除非程序结束,如果组件越来越大,那么程序所占用的空间和消耗的性能就会更大。
所以我们需要局部组件。不用的时候,被垃圾回收。
局部组件的原理是:通过components 把js对象(let先定义的一个对象)注册为组件(components的属性名和对象名一样)
局部组件的两种使用方式
局部组件的第一种使用方式
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="../statics/vue.min.js"></script> </head> <body> <div id="component-demo"> <!--第三步:在根元素当中使用它--> <!--第一种使用方式,会把当前div渲染进DOM--> <my-header></my-header> </div> <script> // 第一步:定义一个局部组件,其实就是一个变量,它是一个object类型 // 备注:属性与全局组件是一样的 let Header = { template: ` <button @click="count++">{{ count }}</button> `, data() { return { count: 0 } } }; new Vue({ el: "#component-demo", // 第二步,需要在根实例当中使用它 components: { 'my-header': Header } }); </script> </body> </html>
局部组件的第二种使用方式
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="../statics/vue.min.js"></script> </head> <body> <div id="component-demo"> </div> <script> // 第一步:定义一个局部组件,其实就是一个变量,它是一个object类型 // 备注:属性与全局组件是一样的 let Header = { template: ` <button @click="count++">{{ count }}</button> `, data() { return { count: 0 } } }; new Vue({ el: "#component-demo", // 第三步 在Vue实例中使用它
// 第二种使用方式 不会将div渲染进DOM,以template为根元素 template: `<my-header></my-header>`, // 第二步,需要在根实例当中使用它 components: { 'my-header': Header } }); </script> </body> </html>
对于 components
对象中的每个属性来说,其属性名就是自定义元素的名字,其属性值就是这个组件的选项对象(let 对象)。
组件的选项
全局组件和局部组件都不可以有#el
因为组件是可复用的 Vue 实例,所以它们与 new Vue()
相同的选项,例如 data
、computed
、watch
、methods
以及生命周期钩子等。仅有的例外是像el
这样选项只有根实例才特有。
如下示例,组件增加了data选项
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="../statics/vue.min.js"></script> </head> <body> <div id="app"> <!--第二步,使用--> <global_component></global_component> </div> <script> // 第一步,注册 Vue.component("global_component", { data: function () { return { count: 0 } }, template: `<button v-on:click="count++">You clicked me {{ count }} times.</button>` }); new Vue({ el: "#app", }); </script> </body> </html>
组件的复用
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="../statics/vue.min.js"></script> </head> <body> <div id="app"> <!--第二步,使用--> <global_component></global_component> <global_component></global_component> <global_component></global_component> </div> <script> // 第一步,注册 Vue.component("global_component", { data: function () { return { count: 0 } }, template: `<button v-on:click="count++">You clicked me {{ count }} times.</button>` }); new Vue({ el: "#app", }); </script> </body> </html>
注意当点击按钮时,每个组件都会各自独立维护它的 count
。因为你每用一次组件,就会有一个它的新实例被创建。
data必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝, 也可以写成如下形式
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="../statics/vue.min.js"></script> </head> <body> <div id="app"> <!--第二步,使用--> <global_component></global_component> <global_component></global_component> <global_component></global_component> </div> <script> // 第一步,注册 Vue.component("global_component", { data(){ return { count: 0 } }, template: `<button v-on:click="count++">You clicked me {{ count }} times.</button>` }); new Vue({ el: "#app", }); </script> </body> </html>
子组件
在局部组件中使用子组件,示例如下
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="../statics/vue.min.js"></script> <style> body { margin: 0; } .box { width: 100%; height: 50px; background-color: #2aabd2; } </style> </head> <body> <div id="component-demo"> </div> <script>
// 第一步:定义局部组件和子组件,并在局部组件中使用子组件 // 定义一个局部组件,其实就是一个变量,它是一个object类型 // 属性与全局组件是一样的 let Fcontent = { template: ` <div> <span>这是头条</span> </div> ` }; let Header = { template: ` <div v-bind:class='{box: isBox}'> <button @click="count++">{{ count }}</button> <first-content></first-content> // 在局部组件中使用子组件的元素 </div> `, data() { return { count: 0, isBox: true } }, components: { 'first-content': Fcontent // 在局部组件中注册子组件 } }; new Vue({ el: "#component-demo", // 第二种使用方式,不会将div渲染进DOM,以template为根元素 template: `<my-header></my-header>`, // 第二步,需要在根实例当中使用局部组件 components: { 'my-header': Header } }); </script> </body> </html>
组件之间同学
父子组件通信
1.定义子组件和父组件,在父组件中通过components注册子组件获得自定义元素
2.在父组件的template中使用子组件对应的自定义元素,同时绑定一个自定义属性,传入要传入的参数
3.子组件中通过props定义一个数组来接受父组件传过来的自定义属性,并在自己的template中渲染
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="../statics/vue.min.js"></script> <style> body { margin: 0; } .box { width: 100%; height: 50px; background-color: #2aabd2; } </style> </head> <body> <div id="component-demo"> </div> <script> // 定义一个局部组件,其实就是一个变量,它是一个object类型 // 属性与全局组件是一样的 let Fcontent = { template: ` <div> <span>这是头条</span> {{ fdata }} </div> `, props: ['fdata'] }; let Header = { template: ` <div v-bind:class='{box: isBox}'> <button @click="count++">{{ count }}</button> <first-content :fdata="fathData"></first-content> </div> `, data() { return { count: 0, isBox: true, fathData: "我是你爸爸~~~" } }, components: { 'first-content': Fcontent } }; new Vue({ el: "#component-demo", // 第二种使用方式,不会将div渲染进DOM,以template为根元素 template: `<my-header></my-header>`, // 第二步,需要在根实例当中使用它 components: { 'my-header': Header } }); </script> </body> </html>
子父组件通信
1.定义子组件和父组件,在父组件中通过components注册子组件获得自定义元素
2.在父组件的template中使用子组件对应的自定义元素,同时mount的时候监听一个自定义事件,没有子组件触发则永远不会执行,后面加一个回调函数,进行各种运算
3.子组件 绑定一个click事件,这个事件对应一个函数,函数内通过 this.$emit(父组件监听的事件),value)发送事件给父组件(在父组件上触发一个事件)
备注:$emit是一个内建的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | <!DOCTYPE html> <html lang= "en" > <head> <meta charset= "UTF-8" > <title>Title</title> <script src= "../statics/vue.min.js" ></script> <style> body { margin: 0; } .box { width: 100%; height: 50px; background-color: #2aabd2; } </style> </head> <body> <div id= "component-demo" > </div> <script> // 定义一个局部组件,其实就是一个变量,它是一个object类型 // 属性与全局组件是一样的 let Fcontent = { template: ` <div> <button v- on :click= "myClick" >放大父组件字体</button> </div> `, methods: { myClick: function () { console.log( this ); this .$emit( 'change-font' , 0.1); console.log( this ); } } }; let Header = { /* template: ` <div v-bind:class='{box: isBox}'> <first-content v-on:change-font="changeFont"></first-content> <span :style="{ fontSize: postFontSize + 'em' }">Hello Vue</span> </div> `, */ template: ` <div v-bind: class = '{box: isBox}' > <first-content v- on :change-font= "postFontSize += $event" ></first-content> <span :style= "{ fontSize: postFontSize + 'em' }" >Hello Vue</span> </div> `, data() { return { count: 0, isBox: true , fathData: "我是你爸爸~~~" , postFontSize: 1 } }, components: { 'first-content' : Fcontent }, methods: { //changeFont: function (value) { // this.postFontSize += value; //} } }; const VM = new Vue({ el: "#component-demo" , // 第二种使用方式,不会将div渲染进DOM,以template为根元素 template: `<my-header></my-header>`, // 第二步,需要在根实例当中使用它 components: { 'my-header' : Header } }); </script> </body> </html> |
课堂例子:
非关系组件之间通信
一个监听
一个emit触发
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | <!DOCTYPE html> <html lang= "en" > <head> <meta charset= "UTF-8" > <title>Title</title> <script src= "../statics/vue.min.js" ></script> </head> <body> <div id= "app" > <com-main></com-main> </div> <script> let pizza = new Vue(); //1.创建一个中央事件总线 let Alex = { template: ` <div> <button @click= "alexClick" >点击向沛齐道歉</button> </div> `, methods: { alexClick: function () { pizza.$emit( "alex_apo" , "原谅我吧,请你大保健~~~" ); //2.通过中央事件总线的emit发送一个别人的监听事件和要发送的内容 } }, }; let peiQi = { template: ` <div v-show= "isShow" >原谅你了~~~</div> `, mounted () { pizza.$ on ( "alex_apo" , function (alexsay) { //3.通过中央事件总线的$.on监听一个事件(等待别人发送过来触发),同时跟一个回调函数,来处理发过来的内容 if ( alexsay ) { console.log( "原谅你了~~~" ); } }); }, data () { return { isShow: false }; } }; let App = { // 蓝色字体为组件编程的编程习惯或者说是编程规范 template: ` <div id= "app" > <alex></alex> <peiqi></peiqi> </div> `, components: { 'alex' : Alex, 'peiqi' : peiQi } }; new Vue({ el: "#app" , template: '<app></app>' , components: { 'app' : App, }, }) </script> </body> </html> |
组件编程规范
见上面例子的蓝色字体
混入
很多个组件用的方法都一样的时候,可以用混入,提高了代码的重用性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | <!DOCTYPE html> <html lang= "en" > <head> <meta charset= "UTF-8" > <title>Title</title> <script src= "../statics/vue.js" ></script> </head> <body> <div id= "mixin-demo" > <my-alex></my-alex> <p></p> <my-peiqi></my-peiqi> </div> <script> let mixs = { methods: { show: function (name) { console.log(`${name} is here!`); }, hide: function (name) { console.log(`${name} is gone!`); }, } }; // Vue.component("my-comp", { // template: ` // <div><button v-on:click="showAlex">点我显示alex</button></div> // `, // methods: { // showAlex: function () { // alert("alex is here!"); // }, // } // }); // // new Vue({ // el: "#mixin-demo", // }); let myAlex = { template: ` <div> <button @click= "show('alex')" >点我显示alex</button> <button @click= "hide('alex')" >点我隐藏alex</button> </div> `, // methods: { // show: function (name) { // console.log(`${name} is here!`); // }, // hide: function (name) { // console.log(`${name} is gone!`); // } // } mixins: [mixs], }; let myPeiqi = { template: ` <div> <button @mouseenter= "show('peiqi')" >鼠标移入显示沛齐</button> <button @mouseleave= "hide('peiqi')" >鼠标离开隐藏沛齐</button> </div> `, // methods: { // show: function (name) { // console.log(`${name} is here!`); // }, // hide: function (name) { // console.log(`${name} is gone!`); // } // }, mixins: [mixs], }; new Vue({ el: "#mixin-demo" , components: { "my-alex" : myAlex, "my-peiqi" : myPeiqi, } }) </script> </body> </html> |
插槽slot
有时候我们需要向组件传递一些数据,这时候可以使用插槽
即模板提供统一的格式,剩下的内容各自去填充,slot作为内容分发的一个接口
普通插槽
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | <!DOCTYPE html> <html lang= "en" > <head> <meta charset= "UTF-8" > <title>Title</title> <style> .nav-link { width: 100px; height: 100px; background-color: #2aabd2; float : left; margin-left: 5px; text-align: center; line-height: 100px; } </style> <script src= "../statics/vue.js" ></script> </head> <body> <div id= "app01" > <com-content>登录</com-content> <com-content>注册</com-content> <com-content>最热</com-content> <com-content>段子</com-content> <com-content>42区</com-content> <com-content>图片</com-content> </div> <script> Vue.component( 'com-content' , { template: ` <div class = "nav-link" > <slot></slot> </div> ` }); new Vue({ el: "#app01" , }) </script> </body> </html> |
具名插槽
有些时候我们需要多个插槽,可以把内容和slot通过名字绑定,没有指定名字的内容被不具名的slot接收。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | <!DOCTYPE html> <html lang= "en" > <head> <meta charset= "UTF-8" > <title>Title</title> <style> .nav-link { width: 100px; height: 100px; background-color: #2aabd2; float : left; margin-left: 5px; text-align: center; line-height: 100px; } </style> <script src= "../statics/vue.js" ></script> </head> <body> <div id= "app01" > < base -layout> <template slot= "header" > <h1>这是标题栏</h1> </template> <template> <h2>这是内容</h2> </template> <template slot= "footer" > <h3>这是页脚</h3> </template> </ base -layout> </div> <script> let baseLayout = { template: ` <div class = "container" > <header> <slot name= "header" ></slot> </header> <main><slot></slot></main> <footer> <slot name= "footer" ></slot> </footer> </div> ` }; new Vue({ el: "#app01" , components: { "base-layout" : baseLayout } }) </script> </body> </html> |
我们还是可以保留一个未命名插槽,这个插槽是默认插槽,也就是说它会作为所有未匹配到插槽的内容的统一出口
使用组件实现路飞导航栏
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="../statics/vue.min.js"></script> <!-- 引入样式 --> <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css"> <!-- 引入组件库 --> <script src="https://unpkg.com/element-ui/lib/index.js"></script> <style> body { margin: 0; padding: 0; } .header { position: fixed; top: 0; left: 0; width: 100%; } .el-menu { display: flex; align-items: center; justify-content: center; } .footer { position: fixed; bottom: 0; left: 0; width: 100%; } .header img { position: absolute; left: 80px; top: -4px; width: 118px; height: 70px; z-index: 999; } </style> </head> <body> <div id="app"> </div> <template id="header"> <div class="header"> <img src="https://www.luffycity.com/static/img/head-logo.a7cedf3.svg"/> <el-menu :default-active="activeIndex" class="el-menu-demo" mode="horizontal"> <el-menu-item index="1">首页</el-menu-item> <el-menu-item index="2">免费课程</el-menu-item> <el-menu-item index="3">轻课</el-menu-item> <el-menu-item index="4">学位课程</el-menu-item> <el-menu-item index="5">智能题库</el-menu-item> <el-menu-item index="6">公开课</el-menu-item> <el-menu-item index="7">内部教材</el-menu-item> <el-menu-item index="8">老男孩教育</el-menu-item> </el-menu> </div> </template> <template id="footer"> <div class="footer"> <el-menu class="el-menu-demo" mode="horizontal" background-color="black"> <el-menu-item index="1">关于我们</el-menu-item> <el-menu-item index="2">联系我们</el-menu-item> <el-menu-item index="3">商业合作</el-menu-item> <el-menu-item index="4">帮助中心</el-menu-item> <el-menu-item index="5">意见反馈</el-menu-item> <el-menu-item index="6">新手指南</el-menu-item> </el-menu> </div> </template> <script> let pageHeader = { template: "#header", data() { return { activeIndex: "1", } } }; let pageFooter = { template: "#footer", }; let App = { template: ` <div> <div> <page-header></page-header> </div> <div> <page-footer></page-footer> </div> </div> `, components: { 'page-header': pageHeader, 'page-footer': pageFooter, } }; new Vue({ el: "#app", template: `<app></app>`, components: { 'app': App, } }) </script> </body> </html>
参考
https://pizzali.github.io/2018/10/27/Vue-js%E7%BB%84%E4%BB%B6%E7%B3%BB%E7%BB%9F/#more
【推荐】2025 HarmonyOS 鸿蒙创新赛正式启动,百万大奖等你挑战
【推荐】博客园的心动:当一群程序员决定开源共建一个真诚相亲平台
【推荐】开源 Linux 服务器运维管理面板 1Panel V2 版本正式发布
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何通过向量化技术比较两段文本是否相似?
· 35+程序员的转型之路:经济寒冬中的希望与策略
· JavaScript中如何遍历对象?
· 领域模型应用
· 记一次 ADL 导致的 C++ 代码编译错误
· 独立项目运营一周年经验分享
· 神解释:为什么程序员怕改需求?
· 一款开源免费、通用的 WPF 主题控件包
· 独立开发,这条路可行吗?
· 【定时任务核心】究竟是谁在负责盯着时间,并在恰当时机触发任务?