前端基础之《Vue(5)—组件基础(1)》
一、什么是组件化
1、理解组件化
组件是HTML的扩展,使用粒度较小的HTML元素封装成粒度更大的标签(Vue组件)。可以实现快速开发、代码复用、提升可维护性。
相当于盖房子,用预制板,不是用一块块砖,一天可以盖好几层。
对Vue组件来讲,最重要的是视图结构,所以我们在封装Vue组件时,可以使用大多数的Vue选项。
视图结构是更小粒度的HTML标签。
2、如何自定义组件
一个组件,你可以理解成是由一组选项构成的,在封装自定义组件时,你可以使用大多数的Vue选项属性,比如:data、template、methods等。
组件封装后,必须要注册(局部注册,或,全局注册)才能在Vue的地盘中使用,注册自定义组件时,组件名称必须由多个单词用中划线连接。
对一个组件来讲,最重要的选项是template选项,用于指定组件的视图结构,在视图结构中你可以使用任意指令。
对一个组件来讲,你可以使用props选项,用于接收父组件传递过来的自定义属性,在组件内部用this访问它们。
对一个组件来讲,你可以使用this.$emit('自定义事件','数据')触发父组件给的自定义事件,并回传数据给父组件。
3、如何进行组件注册
全局注册:Vue.component('xx-yy', '原材料选项')。全局注册的组件,一次注册,在任何其它组件中都可以使用。
局部注册:components: {'xx-yy': '原材料选项'}。局部注册的组件,只能在当前组件作用域中使用。
4、vue的根组件
// 根组件(被new的,实例化)
const app = new Vue({el: '#app'
})
5、组件化的三大技术
自定义属性
自定义事件
自定义插槽
vue中有三个指令有简写:
v-bind:“:”
v-on:“@”
v-slot:#default
正好对应上面三个技术。
二、封装一个评分组件
例子代码:
<html>
<head><title>组件基础-1</title><style>.score {display: inline-block;}.score>span {display: inline-block;width: 50px;height: 50px;background: url(./assets/star.png) center center / 50px 50px;cursor: pointer;}.score>span.on {background: url(./assets/star-on.png) center center / 50px 50px;}</style>
</head>
<body><div id="app"><!-- v-bind:childnum,自定义属性绑定一个父组件变量,要在子组件的props中接收的 --><!-- v-on:abc,自定义事件,用于接收子组件回传回来的数据 --><score-1 v-bind:childnum="num" v-on:abc="getChildNum"></score-1></div><script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script><!-- 评分组件的视图结构 --><script type="text/x-template" id="score"><!-- 指定视图结构 --><div class="score"><span v-for='(i, index) in 5' v-bind:class='{on: childnum>=i}' v-on:click='changexingxing(i)'></span> <!-- 这是一个布尔值,i是1到5,只有num大于等于i的才显示 --></div></script><script>// 组件原材料(视图、接收父组件传递过来的自定义属性)const score = {// 指定当前组件的视图结构template: '#score',// 用于接收父组件传递过来的自定义属性,用this访问它们props: {// 接收childnum,指定数字类型,默认为0childnum: {type: Number, default: 0}},// 生命周期时候讲mounted() {console.log('score num', this.childnum)},methods: {changexingxing(i) {console.log('---clicked', i)// 在这里能让父组件中的num更新,那么分数界面就会变化// 触发自定义事件abc,同时回传i回去this.$emit('abc', i)}}}// 根组件(被new的,实例化)const app = new Vue({el: '#app',data: {num: 0},// 局部注册组件components: {// 组件名字必须是2个以上的单词(可以用横杠分割)'score-1': score},methods: {getChildNum(ev) {console.log('---收到了score子组件回传的数据', ev)this.num = ev}}})</script></body>
</html>
1、自己写一个组件(步骤分解)
(1)定义组件的视图结构
固定写法
<!-- 评分组件的视图结构 -->
<script type="text/x-template" id="score"><!-- 指定视图结构 --><div class="score"><span v-for='(i, index) in 5' v-bind:class='{on: childnum>=i}' v-on:click='changexingxing(i)'></span> <!-- 这是一个布尔值,i是1到5,只有num大于等于i的才显示 --></div></script>
(2)用一个const常量关联这个视图结构的id
template: '#score'
// 组件原材料(视图、接收父组件传递过来的自定义属性)
const score = {// 指定当前组件的视图结构template: '#score',// 用于接收父组件传递过来的自定义属性,用this访问它们props: {// 接收childnum,指定数字类型,默认为0childnum: {type: Number, default: 0}},// 生命周期时候讲mounted() {console.log('score num', this.childnum)},methods: {changexingxing(i) {console.log('---clicked', i)// 在这里能让父组件中的num更新,那么分数界面就会变化// 触发自定义事件abc,同时回传i回去this.$emit('abc', i)}}
}
(3)注册组件
在app根组件的components选项中关联这个常量
// 根组件(被new的,实例化)
const app = new Vue({el: '#app',data: {num: 0},// 局部注册组件components: {// 组件名字必须是2个以上的单词(可以用横杠分割)'score-1': score},methods: {getChildNum(ev) {console.log('---收到了score子组件回传的数据', ev)this.num = ev}}
})
(4)定义组件名称
'score-1': score
(5)像HTML标签一样使用组件名称
<score-1 v-bind:childnum="num" v-on:abc="getChildNum"></score-1>
在自定义组件名称标签里的属性,都是自定义属性。
在自定义组件名称标签里的事件,都是自定义事件。
2、自定义属性
(1)在组件名称标签上自定义一个属性名称,绑定父组件的变量
v-bind:childnum="num"
(2)子组件里用props选项接收父组件的变量
// 用于接收父组件传递过来的自定义属性,用this访问它们
props: {// 接收childnum,指定数字类型,默认为0childnum: {type: Number, default: 0}
},
(3)在视图里使用这个子组件的自定义变量
<span v-for='(i, index) in 5' v-bind:class='{on: childnum>=i}' v-on:click='changexingxing(i)'></span>
3、自定义事件
(1)在组件名称标签上自定义一个事件abc,关联父组件的一个方法
v-on:abc="getChildNum"
(2)在子组件视图里调用子组件自己的方法,触发自定义事件,传递参数
changexingxing(i) {console.log('---clicked', i)// 在这里能让父组件中的num更新,那么分数界面就会变化// 触发自定义事件abc,同时回传i回去this.$emit('abc', i)
}
(3)父组件定义方法接收参数
getChildNum(ev) {console.log('---收到了score子组件回传的数据', ev)this.num = ev
}
4、需要注意的点
这个变量、方法是属于哪个组件的,就定义在哪里。
父组件向子组件传递参数。
子组件里修改父组件的变量。
子组件通过自己视图结构里的点击事件,获取数据。然后触发自定义事件(调用父组件方法),将数据传给父组件,修改父组件里的变量。然后子组件已经通过自定义属性绑定了父组件的这个变量,获取到这个变量值,再修改子组件视图结构里的展示。
三、关于组件间关系与通信
1、约定:在MVVM框架,当我们谈论“组件”这个概念时,通常指的是自定义组件。比如当在A组件的视图结构中使用到了B组件,这就形成了组件关系(父子关系)。那么A就是B的父组件,B就是A的子组件。当组件足够多的时候,组件间关系就会形成“组件树”。
2、通信:在Vue中,所谓“通信”就是组件之间的数据交互。父组件向子组件通信,使用自定义属性,在子组件中使用props接收。子组件向父组件通信,使用自定义事件,在子组件中使用this.$emit('自定义事件','数据')回传。
注意:在视图模板中,不要用this,因为视图模板是要被vue编译器编译的,它是从上下文拿变量的,而不是通过this拿的。
四、组件复用的问题
1、slot标签
在子组件的视图结构里加入slot标签,类似于一个占位的功能
<script type="text/x-template" id="score"><!-- 指定视图结构 --><div class="score"><slot name='default'></slot><span v-for='(i, index) in 5' v-bind:class='{on: value>=i}' v-on:click='$emit("input", i)'></span> <!-- 这是一个布尔值,i是1到5,只有num大于等于i的才显示 --><slot name='xxx'></slot></div></script>
添加“配送速度”文本
<div id="app"><!-- v-bind:childnum,自定义属性绑定一个父组件变量,要在子组件的props中接收的 --><!-- v-on:abc,自定义事件,用于接收子组件回传回来的数据 --><score-1 v-bind:childnum="num" v-on:abc="getChildNum">配送速度:</score-1>
</div>
显示效果
如果把文本换成其他的,视图结构里会自动变。
默认添加到default的插槽中。
五、简化自定义属性、自定义事件写法
<html>
<head><title>组件基础-2</title><style>.score {display: inline-block;}.score>span {display: inline-block;width: 50px;height: 50px;background: url(./assets/star.png) center center / 50px 50px;cursor: pointer;}.score>span.on {background: url(./assets/star-on.png) center center / 50px 50px;}</style>
</head>
<body><div id="app"><!-- 在指令表达式中使用$event拿到事件对象 --><score-1 v-bind:value="num" v-on:input='num = $event'>配送速度:</score-1></div><!-- <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script> --><script src="vue-2.7.16.js"></script><!-- 引入样式 --><!-- <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css"> --><link rel="stylesheet" href="index.css"><!-- 引入组件库 --><!-- <script src="https://unpkg.com/element-ui/lib/index.js"></script> --><script src="index.js"></script><!-- 评分组件的视图结构 --><script type="text/x-template" id="score"><!-- 指定视图结构 --><div class="score"><slot name='default'></slot><span v-for='(i, index) in 5' v-bind:class='{on: value>=i}' v-on:click='$emit("input", i)'></span> <!-- 这是一个布尔值,i是1到5,只有num大于等于i的才显示 --><slot name='xxx'></slot></div></script><script>// 组件原材料(视图、接收父组件传递过来的自定义属性)const score = {// 指定当前组件的视图结构template: '#score',// 用于接收父组件传递过来的自定义属性,用this访问它们props: {value: {type: Number, default: 0}}}// 根组件(被new的,实例化)const app = new Vue({el: '#app',data: {num: 0},// 局部注册组件components: {// 组件名字必须是2个以上的单词(可以用横杠分割)'score-1': score}})</script></body>
</html>