1、组件的概念及作用:
组件(Component)是Vue.js最强大的功能之一。. 组件可以扩展HTML元素,封装可以重复使用的代码。. 在较高层次上,组件就是自定义元素,Vue.js的编辑器为它添加特殊功能。
一般的前端页面由头部、中间区域、底部构成,中间区域又可以分为各种的功能模块,例如轮播图、商品展示区等等。vue组件的作用就是将不同的功能区域独立的封装起来,可以封装的部分有结构、样式、逻辑代码。这样封装的好处就是当页面中的某个功能出现问题的时候我们只需要找到对应的组件进行维护处理就行了,而不再需要考虑整个页面,这样既提高了功能的复用性也提高了功能的可维护性。
2、组件基础
1、组件的基本使用
1. <div id="app"> 2. <p>p标签内容</p> 3. <cpc></cpc> 4. </div>
在app元素中(或者叫vue实例)里面存在一个叫<cpc></cpc>的自定义标签,这个标签是我们自定义的标签,实际上在HTML中是不存在的。该标签其实就是组件的标签名,我们只需要在特定位置书写该标签就能使用到该标签所对应的组件。
2、 组件本质
组件在本质上是个可复用的vue实例。它们可以和Vue根组件一样有相同的选项,例如:data、methods、以及生命周期钩子等,只有el选项是vue根实例特有的,因为el代表的是挂载元素, 也就是说根元素是需要挂载到页面上某个元素身上的。而组件是需要被其他组件或者是根实例使用的因此不需要挂载到页面中的某个元素身上。
3、组件的命名规则
组件的命名规则有两种方式:
kebab-case(驼峰命名):"my-component"
PascalCase(帕斯卡命名):"MyComponent"
【注意】无论采用上面哪一种的命名方式,在使用组件时都只能使用kebab-case(驼峰命名)来使用组件。
Vue.component("my-component",{/*选项对象*/}) Vue.component("MyComponent",{/*选项对象*/})
4、template选项
template,模板的意思。用于设置组件的结构,最终通过标签被引入根实例或者是其他组件中。
【注意】在template中只能设置一个根元素,也就是说只能有一个div标签,其他标签都需要写在div标签中。此外,在template中也可以设置插值表达式(也就是mustache表达式)
Vue.component("my-component",{ template:` <div> <h2>my-component组件:{{1+2*4}}</h2> </div> ` })
5、data选项
data选项用于存储组件的数据,和根实例不同,组件的data选项必须是一个函数,数据设置在返回值对象中。
data选项为什么是一个函数(或者叫方法)?是因为这种实现方式可以确保每个组件实例都可以维护一份被返回对象的拷贝,不会互相影响。因为在实际项目中组件可能会被多次引用,每次引用都会调用一次组件结构,这时候为了确保每个被引用组件的数据是相互独立的而不是公用的,就需要使用作用域对其进行隔离,而data是个函数,函数内部是有作用域的,当我们在函数内部声明了数据以后利用该函数进行多次调用那么内部相当于形成了多个独立的作用域,每个作用域中的数据都是独立的。
Vue.component("my-component",{ template:` <div> <h2>{{title}}</h2> <p>{{content}}</p> </div> `, data(){ return { title:'组件中的数据', content:'组件中的内容' } } })
3、组件的分类:
在vue中组件可以分为全局组件和局部组件。
全局组件:所谓的全局组件就是不仅仅可以在一个vue实例中使用,在其他vue实例或者组件中也可以使用的组件。
局部组件:所谓的局部组件就是只能在某个vue实例中使用的组件,在其他的vue实例中不能使用的组件。
4、全局组件
在组件基础中演示的例子就是注册全局组件的步骤,因此这里不再赘述。
5、局部组件
局部组件在注册后只能在当前实例或者是组件中使用。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>组件</title> </head> <body> <script src="../js/vue.js"></script> <div id="app"> <my-component-a></my-component-a> <my-component-b></my-component-b> </div> <script> const app = new Vue({ el:"#app", data:{ message:"你好啊!" }, components:{ "my-component-a":{ template:'<h2>{{title}}</h2>', data(){ return{ title:'这是组件a的标题' } } }, "my-component-b":{ template: '<h2>{{title}}</h2>', data(){ return{ title: '这是组件b的标题' } } } } }) </script> </body> </html>
代码运行结果:
6、组件抽取
所谓的抽取组件实际上是单独设置选项对象,这样是为了简化代码,方便阅读和维护。
可以将组件抽取到外面,在需要注册时直接将组件的对象注册到其他组件或者实例中即可。
组件抽取有两种方式:
1、不使用标签的选项抽取方式(全部抽取);这种方式可以将组件的左右选项,例如template、data、methods全部抽取出来。
2、使用<template><template/>抽取,这种抽取方式仅仅只能抽取template选项的内容。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>组件</title> </head> <body> <script src="../js/vue.js"></script> <div id="app"> <my-component-a></my-component-a> <my-component-b></my-component-b> </div> <!--使用template标签抽取--> <template id="temp"> <div> <h2>{{title}}</h2> </div> </template> <script> /*组件B模板抽取*/ let MyConmponentA = { template:'<h2>{{title}}</h2>', data(){ return{ title:'这是组件A的标题' } } } const app = new Vue({ el:"#app", data:{ message:"你好啊!" }, //注册子组件 components:{ //A子组件使用的是全部抽取 "my-component-a":MyConmponentA, //B组件使用的是只抽取template选项 "my-component-b":{ template: `#temp`, data(){ return{ title: '这是组件B的标题' } } } } }) </script> </body> </html>
7、组件通讯
什么是组件通讯,为什么要组件通讯?
上面说到组件和组件之间是完全独立的功能,在局部注册组件中我们是将组件注册到了vue根组件中,那么子组件是如何获取根足件中的数据的呢?以及子组件与子组件之间是如何相互获取数据的呢?我们将子组件获取根组件、子组件与子组件之间数据获取的过程叫组件通讯。
1、父组件向子组件传递数据
props接收父组件传递来的值。
【注意】props与data不要存在同名属性。
props命名规则:假设props中的变量是驼峰命名,即props['myTitle'] 那么在使用子组件的时候需要这么写<my-component v-bind:my-title="myTitle"></my-component>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>组件</title> </head> <body> <script src="../js/vue.js"></script> <div id="app"> <my-component-b v-bind:value1="item.message" v-bind:value2="item.language"></my-component-b> </div> <!--使用template标签抽取--> <template id="temp"> <div> <h2>{{value1}}</h2> <p>{{value2}}</p> </div> </template> <script> const app = new Vue({ el:"#app", data:{ item:{ message:"你好啊!", language:'vue' } }, //注册子组件 components:{ "my-component-b":{ props:['value1','value2'], template: `#temp`, data(){ return{ title: '这是组件B的标题' } } } } }) </script> </body> </html>
components:{ "my-component-b":{ props:{ value1:{ type:String, default:'value1' }, value2:{ type:String, default: 'value2' } }, template: `#temp`, data(){ return{ title: '这是组件B的标题' } } } }
代码运行结果:
首先需要明白父组件向子组件传递数据是怎么传递的,对于初学vue的小伙伴来说数据传递这部分算是比较难理解的地方了,最初开始学的时候我也是一头雾水,哈哈!好,我们暂时不考虑组件通讯这个问题。假设我们的组件模板是这样的:
<template id="temp">
<div>
<h2>我是标题</h2>
<p>我是内容</p>
</div>
</template>
h2标签和p标签中的内容都是固定死的,此时我们在vue的实例中使用组件my-component-b
<div id="app">
<my-component-b></my-component-b>
</div>
这样,在浏览器中我们就能看见输出的两行字:这两行字是我们在模板中定义好的。但是在实际开发中往往是父组件向服务器发送请求获取数据,然后将数据传递给子组件的。也就是说子组件中的模板中的数据是和服务端挂钩的,用jio指头想想都是不能写死的。所以子组件模板中的数据都是动态的。现在我们还是从使用组件开始:
<div id="app">
<my-component-b v-bind:value1="item.message" v-bind:value2="item.language"></my-component-b>
</div>
上面这段代码是将vue组件中的数据分别动态绑定到了value1和value2上去了,需要一下,value1和value2这两个变量的作用范围是vue实例,现在到props出场的时候了,props的作用就是获取vue实例中的value1和value2这两个变量,获取后为vue子组件所用:
components:{
"my-component-b":{
props:['value1','value2'],
template: `#temp`,
data(){
return{
title: '这是组件B的标题'
}
}
}
}
上面这段代码就是获取了value1和value2两个变量,然后组件既然获取了这两个数据,那么组件中的模板也就可以使用这两个值了:
<template id="temp">
<div>
<h2>{{value1}}</h2>
<p>{{value2}}</p>
</div>
</template>
这样就能在浏览器获取到父组件的值了。
2、子组件向父组件传数据
1、使用$emit('事件名称'),向父组件传值。
上面说到父组件向子组件传值,子组件处理完这些数据后可能会返回给父组件一些数据,此时就不能使用props 来处理了而是通过自定义事件来实现。因为子组件什么时候处理完数据是不确定的,因此需要捕获子组件触发的事件得知子组件的状态。简单说就是当子组件处理数据完成之后触发事件,父组件通过$emit('事件名称')捕获子组件发出的事件进行相应的处理即可。
以购物车为例,购物车中包含着全部的商品,每个商品都代表着一个子组件,也就是说需要将父组件中的商品数据传递到子组件中,购物车中有N个商品那么就有N个子组件。改变每个子组件的数量后最终在购物车内部统计总数量。下面是效果图:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>组件</title> </head> <body> <script src="../js/vue.js"></script> <div id="app"> <h2>购物车</h2> <!--子组件实例,获取vue组件的数据,v-bind用于接受vue组件传递来的name--> <my-component v-for="fruit in fruits" v-bind:fname="fruit.name" :key="fruit.id" @btnclick="total++"></my-component> <p>商品的总数量是:{{total}}</p> </div> <!--子组件需要接受父组件的值--> <template id="temp"> <div> <h2>商品的名称是:{{fname}},数量是:{{count}} <button @click="btnclick">+1</button> </h2> </div> </template> <script> const app = new Vue({ el:"#app", data:{ fruits:[ { id:1, name:'香蕉', }, { id:2, name:'苹果', }, { id:3, name:'橘子', }, ], total:0 }, //注册子组件 components:{ "my-component":{ props:[ 'fname' ], template: `#temp`, data(){ return{ count:0 } }, methods:{ /*触发事件时数量+1*/ btnclick(){ /*接受到事件后将事件发射*/ this.$emit('btnclick') this.count++ } } } } }) </script> </body> </html>
<my-component v-for="fruit in fruits" v-bind:name="fruit.name" :key="fruit.id" @btnclick="total++"></my-component>
v-for="fruit in fruits":遍历父组件中的fruits对象。
v-bind:fname="fruit.name":将fruit对象的name属性绑定到fname变量上。目的是为了使用props将该变量传递到子组件上。当子组件获取到fname变量的时候,在子组件模板中就可以使用啦!
:key="fruit.id":相当于是每个组件唯一的标识。
<template id="temp">
<div>
<h2>商品的名称是:{{fname}},数量是:{{count}}
<button @click="btnclick">+1</button>
</h2>
</div>
</template>
商品的名称是:{{fname}}:fname是获取的父组件中的fname变量。
数量是:{{count}}:count是子组件的值。
<button @click="btnclick">+1</button>:点击按钮触发事件btnclick
methods:{
/*触发事件时数量+1*/
btnclick(){
/*接受到事件后将事件发射*/
this.$emit('btnclick')
this.count++
}
}
this.$emit('btnclick'):子组件在触发btnclick事件后将该事件发射出去。
<my-component v-for="fruit in fruits" v-bind:fname="fruit.name" :key="fruit.id" @btnclick="total++"></my-component>
@btnclick="total++":父组件获捕获到btnclick事件,将其作用域内的total值+1。
<p>商品的总数量是:{{total}}</p>
{{total}}:获取父组件vue的值。