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是一个内建的方法
<!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触发
<!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>
组件编程规范
见上面例子的蓝色字体
混入
很多个组件用的方法都一样的时候,可以用混入,提高了代码的重用性
<!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作为内容分发的一个接口
普通插槽
<!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接收。
<!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

浙公网安备 33010602011771号