1. 什么是组件化
人在面对复杂问题的时候,习惯将复杂的问题按照一定的规律和逻辑拆成一个个小的问题来慢慢解决,当所有小的问题解决了,那么原本那个大的复杂问题也迎刃而解了。组件化也有类似的思想:
如果我们将一个页面的所有逻辑全部放在一起处理,那么处理起来将会变得比较复杂,而且也不利于后期的维护和扩展。但是,如果我们将一整个页面拆解成一个个小的功能块,每个功能块完成属于自己那部分独立的功能,那么之后整个页面的维护和扩展就会变的简单的多。
那么,下面我们概括下组件化思想:
-
它提供了一种抽象,让我们可以开发出一个个独立且可复用的组件来构建我们的应用
-
任何的应用都可以被抽象成一颗组件树
2. 组件的使用
2.1 基本使用
组件的使用分为三个步骤:
- 创建组件构造器
- 注册组件
- 使用组件
<div id="app">
<!-- 3.使用组件-->
<my-cpn></my-cpn>
</div>
<script src="../js/vue.js"></script>
<script>
//1.创建组件构造器
const myComponent = Vue.extend({
template: `<div>
<h2>我是组件</h2>
<p>哈啊哈哈</p>
</div>`
});
//2.注册组件
Vue.component('my-cpn', myComponent);
const app = new Vue({
el:'#app'
})
</script>
需要注意的是:组件必须挂载在某个Vue实例下,否则将不会生效。
比如我把上面的第三个步骤的使用组件写成这样,第二次使用的组件就不会生效。
<div id="app">
<!-- 3.使用组件-->
第一次使用组件: <my-cpn></my-cpn>
</div>
第二次使用组件:<my-cpn></my-cpn>
2.2 全局组件和局部组件
当我们使用Vue.component( ) 来注册组件时,表示此组件是全局注册的,那么代表该组件可以在任意Vue实例下应用。如上面的组件基本使用的例子就是注册了全局组件,并且可以在创建的Vue实例app中应用。
那么,什么是全局组件呢?
如果我们注册的组件时挂载在某个实例中,那么这个组件就是局部组件。
例如:我们先创建一个组件构造器,然后把组件挂载在其中一个vue实例中,另一个不挂载,然后分别在两处使用这个组件,观察结果:
<div id="app1">
<h2>我是app1实例</h2>
<my-cpn></my-cpn>
</div>
<br>
<div id="app2">
<h2>我是app2实例</h2>
<my-cpn></my-cpn>
</div>
<script src="../js/vue.js"></script>
<script>
//1创建组件构造器
const myComponent = Vue.extend({
template: `<div>
<h2>我是局部组件</h2>
</div>`
});
const app1 = new Vue({
el: '#app1',
components:{
'my-cpn':myComponent
}
});
const app2 = new Vue({
el: '#app2',
});
</script>
结果如下:
![](https://i-blog.csdnimg.cn/blog_migrate/a34154123c47cca4f2c1acaa54d648ff.png)
2.3 父组件与子组件
在前面我们看到了组件树,可以看出组件与组件之间存在着层级关系,而其中一个非常重要的关系就是父子关系。
下面我们通过代码来展示:
<div id="app">
//5. 在vue实例中使用父组件
<parent-cpn></parent-cpn>
<h2>分割--------------------------</h2>
//6. 在vue实例中使用子组件
<child-cpn></child-cpn>
</div>
<script src="../js/vue.js"></script>
<script>
//1.创建子组件构造器
const childComponent = Vue.extend({
template: `<div>
<h2>我是子组件</h2>
</div>`
});
//2.创建父组件构造器
const parentComponent = Vue.extend({
template: `<div>
<h2>我是父组件</h2>
<child-cpn></child-cpn>
</div>`,
//3.将子组件挂载到父组件中
components:{
'child-cpn': childComponent
}
});
const app = new Vue({
el:'#app',
//4. 将父组件挂载到vue实例中
components:{
'parent-cpn': parentComponent
}
})
</script>
结果如下:
![](https://i-blog.csdnimg.cn/blog_migrate/a80b84c78e41d87110ed4f35f42150cb.png)
可以看出:子组件是只能在父组件中使用才会被识别
2.4 注册组件的语法糖
上面注册组件的方式可能略显繁琐,vue为了简化这个过程,提供了注册的语法糖。
其实主要是省去了Vue.extend() 的调用,而是直接使用一个对象来代替。
- 语法糖注册全局组件
- 语法糖注册局部组件
<div id="app">
<my-cpn1></my-cpn1>
<my-cpn2></my-cpn2>
</div>
<script src="../js/vue.js"></script>
<script>
//语法糖注册全局组件
Vue.component('my-cpn1',{
template:`<div>我是用语法糖注册的全局组件</div>`
})
const app = new Vue({
el:'#app',
components:{
//语法糖注册局部组件
'my-cpn2':{
template:`<div>我是语法糖注册的局部组件</div>>`
}
}
})
</script>
2.5 模板的分离写法
上面,我们通过语法糖简化了Vue组件注册的过程,但是,还有一个比较麻烦的地方就是template模板的写法,如果我们能将其分离出来,然后挂载到对应的组件上,结构就会变得更加清晰,Vue 给我们提供了标签来抽离模板内容。
<div id="app">
<my-cpn3></my-cpn3>
</div>
<script src="../js/vue.js"></script>
<template id="my-cpn3">
<div>我是模板抽离的写法</div>
</template>
<script>
const app = new Vue({
el:'#app',
components:{
'my-cpn3':{
template:'#my-cpn3'
}
}
})
</script>
3. 父子组件间的通信
在vue开发中,父组件和子组件之间的数据都是相互独立互不干扰的,但是在真实开发中,往往一些数据需要从父组件传递到子组件或者从子组件传递到父组件。那么,Vue中的父子组件时怎样进行通信的呢?
- 通过props向子组件传递数据
- 通过事件向父组件发送消息
下面通过代码演示。
3.1 父组件向子组件传递消息
在vue中,子组件中使用props来接受从父组件那传递过来的数据。
props的值有两种方式:
- 字符串数组,数组中的字符串就是传递时的参数名称
- 对象,对象可以设置传递时的类型,也可以设置默认值等
字符串数组方式:通过 :message="message"将data中的message传递给props中
<div id="app">
<cpn :message="message"></cpn>
</div>
<script src="../js/vue.js"></script>
<template id="cpn">
<div>
<h2>我是子组件</h2>
<h2>{{message}}</h2>
</div>
</template>
<script>
const app = new Vue({
el:'#app',
data:{
message:'hello world'
},
components:{
'cpn':{
template:'#cpn',
props:['message']
}
}
})
</script>
对象方式:当我们需要对数据进行验证或者添加默认值的时候,就需要用对象方式。
<div id="app">
<cpn2 :num="num"></cpn2>
</div>
<script src="../js/vue.js"></script>
<template id="cpn2">
<div>
参数是:{{num}}
</div>
</template>
<script>
const app = new Vue({
el:'#app',
data:{
message:'hello world',
num:200
},
components:{
'cpn2':{
template:'#cpn2',
props:{
num:{
type:Number,
default:100
}
}
}
}
})
</script>
3.2 子组件向父组件发送事件
props用于父组件向子组件传递数据,还有一种比较常见的是子组件传递数据或事件到父组件中。
我们应该如何处理呢?这个时候,我们需要使用自定义事件来完成。
那么,什么时候需要自定义事件呢?
当子组件需要向父组件传递数据时,就要用到自定义事件了。
我们之前学习的v-on不仅仅可以用于监听DOM事件,也可以用于组件间的自定义事件
自定义事件的流程:
- 在子组件中,通过 $emit()来触发事件
- 在父组件中,通过v-on来监听子组件事件
下面通过一个案例来演示:
我们在子组件中定义一个变量counter,并且通过点击事件来改变它的值,最终我们将这个值交给父组件展示
<div id="app">
<cpn2 @increment="getNum" @decrement="getNum"></cpn2>
<h2>点击次数:{{num}}</h2>
</div>
<script src="../js/vue.js"></script>
<template id="cpn2">
<div>
<button @click="increment">+1</button>
<button @click="decrement">-1</button>
</div>
</template>
<script>
const app = new Vue({
el:'#app',
data:{
num:0
},
methods: {
getNum(counter) {
this.num = counter;
}
},
components:{
'cpn2':{
template:'#cpn2',
data(){
return {
counter:0
}
},
methods:{
increment() {
this.counter++;
this.$emit('increment',this.counter)
},
decrement() {
this.counter--;
this.$emit('decrement',this.counter)
}
}
}
}
})
</script>
4. 父子组件间的访问
父组件访问子组件:
- 使用$children
- 使用 $refs
子组件访问父组件:使用 $parent
4.1 父组件访问子组件
4.1.1 $children的用法
<div id="app">
<cpn1></cpn1>
<cpn1></cpn1>
<cpn1></cpn1>
<button @click="showChilren">显示所有子组件</button>
</div>
<template id="cpn1">
<div>我是子组件</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el:'#app',
methods:{
showChilren() {
console.log(this.$children);
for (let child of this.$children){
console.log(child);
}
}
},
components:{
'cpn1':{
template:'#cpn1',
}
}
})
</script>
4.1.2 $refs的用法
$children的缺陷:
- 通过$children访问子组件时,是一个数组类型,访问其中的子组件必须通过索引值
- 但是当子组件过多,我们需要拿到其中一个时,往往不能确定它的索引值,甚至还可能会发生变化
- 有时候,我们想明确获取其中一个特定的组件,这个时候就可以使用$refs
$refs的使用:
-
$refs和ref指令通常是一起使用的
-
首先,我们通过ref给某一个子组件绑定一个特定的ID
-
其次,通过this.$refs.ID就可以访问到该组件了
4.2 子组件访问父组件
如果我们想在子组件中直接访问父组件,可以通过$parent
使用注意:
- 尽管在Vue开发中,我们允许通过$parent来访问父组件,但是在真实开发中尽量不要这样做
- 子组件应该尽量避免直接访问父组件的数据,因为这样耦合度太高了
- 如果我们将子组件放在另外一个组件之内,很可能该父组件没有对应的属性,往往会引起问题
- 另外,更不好做的是通过$parent直接修改父组件的状态,那么父组件中的状态将变得飘忽不定,很不利于我的调试和维护