一、什么是组件化
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,指定数字类型,默认为0
childnum: {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,指定数字类型,默认为0
childnum: {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,指定数字类型,默认为0
childnum: {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 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>
六、简化组件注册写法
改成全局注册
<html>
<head>
<title>组件基础-3</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">
<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>
// 评分组件
Vue.component('score-1',{
template: `
<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>
<slot name='xxx'></slot>
</div>
`,
props: {
value: {type: Number, default: 0}
}
})
const app = new Vue({
el: '#app',
data: {
num: 0
}
})
</script>
</body>
</html>