一篇文章讲清楚Vue组件基础
Vue组件基础
Vue的组件化思想
组件化是Vue中很重要的思想:
- 它提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用
- 任何的应用都会被抽象成一颗组件树
组件化思想的应用:
- 有了组件化的思想,我们在之后的开发中就要充分的利用它。
- 尽可能的将页面拆分成一个个小的、可复用的组件。
- 这样让我们的代码更加方便组织和管理,并且扩展性也更强。
注册组件
全局注册
- Vue.component(‘组件名称’, { }) 第1个参数是标签名称,第2个参数是一个选项对象
- 全局组件注册后,任何vue实例都可以用
<div id="example">
<!-- 2、 组件使用 组件名称 是以HTML标签的形式使用 -->
<my-component></my-component>
</div>
<script> // 注册组件 // 1、 my-component 就是组件中自定义的标签名 Vue.component('my-component', {
template: '<div>A custom component!</div>' }) // 创建根实例 new Vue({
el: '#example' }) </script>
局部注册
<div id="app">
<my-component></my-component>
</div>
<script> // 定义组件的模板 var Child = {
template: '<div>A custom component!</div>' } new Vue({
//局部注册组件 components: {
// <my-component> 将只在父模板可用 一定要在实例上注册了才能在html文件中使用 'my-component': Child } }) </script>
局部注册的组件只能在注册它的Vue实例中使用
组件注意事项
- 组件参数data必须是一个函数,同时要求改函数返回一个对象
- 组件模板必须是单个根元素,当需要写多个HTML标签时,需在最外层套一个div
- 组件模板的内容可以是模板字符串
- 组件可以使用多次
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="app">
<cpn></cpn>
<cpn></cpn>
<cpn></cpn>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script> Vue.component('cpn', {
template: '<button @click="handle">点击了{
{counter}}次</button>', data() {
return {
counter: 0 } }, methods:{
handle() {
this.counter++; } } }) const app = new Vue({
el: "#app", data: {
} }); </script>
</body>
</html>
如上的代码中,名为cpn
的组件被使用了三次,从运行效果可以看出,每个组件的counter变量是独立的
模板的分离写法
上述注册组件的方法,我们将template模板放在了注册组件的内部,这样做会带来两个坏处
- 没有代码补全,撰写模板的过程更麻烦
- 代码看起来更加凌乱
因此一般我们会采用模板的分离写法,nVue提供了两种方案来定义HTML模块内容:
-
使用
<script>
标签 -
使用
<template>
标签
组件之间的通信
父组件向子组件传值
- 父组件发送的形式是以属性的形式绑定值到子组件身上。
- 然后子组件用属性props接收
- 在props中使用驼峰形式,模板中需要使用短横线的形式字符串形式的模板中没有这个限制
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<cpn :c-movies="movies" :c-message="message"></cpn>
</div>
<template id="cpn">
<div>
<h2>{
{cMessage}}</h2>
<div>
<ul>
<li v-for="item in cMovies">{
{item}}</li>
</ul>
</div>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script> const app = new Vue({
el: "#app", data: {
message: '你好啊', movies: ['星际穿越', '火星救援', '星际迷航', '星球大战'] }, components: {
cpn: {
template: '#cpn', data() {
return {
} }, methods: {
}, props: {
cMovies: {
type: Array, default () {
return ['随便什么电影'] } }, cMessage: {
type: String, default: 'aaaaaa' } } } }, }); </script>
</body>
</html>
上面的代码中,props属性采用的是对象写法,好处就是可以进行类型校验,默认值设置等操作,而另外的数组写法则不能进行这些操作:
props: ['cMovies', 'cMessage']
子组件向父组件传值
- 子组件用
$emit()
触发事件 $emit()
第一个参数为 自定义的事件名称 第二个参数为需要传递的数据- 父组件用v-on 监听子组件的事件
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="app">
<div :style='{
fontSize: fontSize + "px"}'>{
{pmsg}}</div>
<!-- 父组件用v-on 监听子组件的事件 这里 enlarge-text 是从 $emit 中的第一个参数对应 handle 为对应的事件处理函数 -->
<menu-item :parr='parr' @enlarge-text='handle($event)'></menu-item>
</div>
<template id="cpn">
<div>
<ul>
<li :key='index' v-for='(item,index) in parr'>{
{item}}</li>
</ul>
<!-- 子组件用$emit()触发事件 第一个参数为 自定义的事件名称 第二个参数为需要传递的数据 -->
<button @click='$emit("enlarge-text", 5)'>扩大父组件中字体大小</button>
<button @click='$emit("enlarge-text", 10)'>扩大父组件中字体大小</button>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script type="text/javascript"> var vm = new Vue({
el: '#app', data: {
pmsg: '父组件中内容', parr: ['apple','orange','banana'], fontSize: 10 }, methods: {
handle: function(val){
// 扩大字体大小 this.fontSize += val; } }, components:{
'menu-item': {
props: ['parr'], template: '#cpn' } } }); </script>
</body>
</html>
兄弟间的传值
- 兄弟之间传递数据需要借助于事件中心,通过事件中心传递数据
- 提供事件中心 var hub = new Vue()
- 传递数据方,通过一个事件触发hub.$emit(方法名,传递的数据)
- 接收数据方,通过mounted(){} 钩子中 触发hub.$on()方法名
- 销毁事件 通过hub.$off()方法名销毁之后无法进行传递数据
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="app">
<div>父组件</div>
<div>
<button @click='handle'>销毁事件</button>
</div>
<test-tom></test-tom>
<test-jerry></test-jerry>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script type="text/javascript"> /* 兄弟组件之间数据传递 */ //1、 提供事件中心 var hub = new Vue(); Vue.component('test-tom', {
data: function(){
return {
num: 0 } }, template: ` <div> <div>TOM:{
{num}}</div> <div> <button @click='handle'>点击</button> </div> </div> `, methods: {
handle: function(){
//2、传递数据方,通过一个事件触发hub.$emit(方法名,传递的数据) 触发兄弟组件的事件 hub.$emit('jerry-event', 2); } }, mounted: function() {
// 3、接收数据方,通过mounted(){} 钩子中 触发hub.$on(方法名 hub.$on('tom-event', (val) => {
this.num += val; }); } }); Vue.component('test-jerry', {
data: function(){
return {
num: 0 } }, template: ` <div> <div>JERRY:{
{num}}</div> <div> <button @click='handle'>点击</button> </div> </div> `, methods: {
handle: function(){
//2、传递数据方,通过一个事件触发hub.$emit(方法名,传递的数据) 触发兄弟组件的事件 hub.$emit('tom-event', 1); } }, mounted: function() {
// 3、接收数据方,通过mounted(){} 钩子中 触发hub.$on()方法名 hub.$on('jerry-event', (val) => {
this.num += val; }); } }); var vm = new Vue({
el: '#app', data: {
}, methods: {
handle: function(){
//4、销毁事件 通过hub.$off()方法名销毁之后无法进行传递数据 hub.$off('tom-event'); hub.$off('jerry-event'); } } }); </script>
</body>
</html>
组件插槽
匿名插槽
-
组件最大的特性就是复用性,而插槽作为组件的扩展大大提高了组件的可复用能力
-
在vue组件的模板中,可用
<slot></slot>
添加一个插槽,插槽内的内容是默认值,当没有被覆盖时,将显示默认值
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="app">
<!-- 此处组件标签内没有其他内容,因此显示默认内容 -->
<cpn></cpn>
<!-- 此处利用span标签覆盖了插槽的默认内容 -->
<cpn><span>这里是一些文字</span></cpn>
<!-- 组件标签里的所有内容都会作为插槽内容显示,因此此处的三个标签都会显示 -->
<cpn>
<b>加粗文字</b>
<i>斜体文字</i>
<span>这里是一些文字</span>
</cpn>
</div>
<template id="cpn">
<div>
<h2>我是组件标题</h2>
<p>我是组件的文本内容</p>
<slot><button type="button">这是按钮</button></slot>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script> const app = new Vue({
el: '#app', data: {
}, components:{
cpn: {
template:'#cpn' } } }) </script>
</body>
</html>
具名插槽
- 当有多个插槽时,我们要想覆盖特定的某个插槽就必须使用名字,这就是具名插槽
- 使用
<slot>
中的 “name” 属性绑定元素
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="app">
<cpn>
<button type="button" slot="left">按钮</button>
<span slot="center"><b>这是中间的标题</b></span>
<a href="" slot="right">一个链接</a>
</cpn>
</div>
<template id="cpn">
<div>
<slot name="left"><span>左边</span></slot>
<slot name="center"><span>中间</span></slot>
<slot name="right"><span>右边</span></slot>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script> const app = new Vue({
el: '#app', data: {
}, components:{
cpn: {
template:'#cpn' } } }) </script>
</body>
</html>
作用域插槽
- 父组件对子组件加工处理
- 既可以复用子组件的slot,又可以使slot内容不一致
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="app">
<!-- 默认是无序列表 -->
<cpn></cpn>
<!-- 此处将内容改为每个元素中间用“-”连接 -->
<cpn>
<template v-slot="slot">
<span v-for="item in slot.data">{
{item}} - </span>
</template>
</cpn>
<!-- 此处将内容改为每个元素中间用“*”连接 -->
<cpn>
<template v-slot="slot">
<span>{
{slot.data.join(' * ')}}</span>
</template>
</cpn>
</div>
<template id="cpn">
<div>
<slot :data="items">
<ul>
<li v-for="item in items">{
{item}}</li>
</ul>
</slot>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script> const app = new Vue({
el: '#app', data: {
}, components:{
cpn: {
template:'#cpn', data() {
return {
items: ['A', 'B', 'C', 'D', 'E', 'F', 'G'] } } } } }) </script>
</body>
</html>
父子组件通信的练习
在接下来的实例中,我将模拟在有网络请求的情况下,用户点击不同的分类tab时显示不通内容的案例。
- 父组件首先模拟向后台请求数据,请求到数据后显示tab列表并显示默认的数据
- 用户可以点击按钮,点击后子组件通知父组件模拟请求对应的数据
- 父组件请求到数据后传给子组件并显示
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<style type="text/css"> .active{
background-color: #00aaff; } .btn{
margin-right: 10px; border: 0; border-radius: 2px; padding-left: 10px; padding-right: 10px; height: 30px; } </style>
<body>
<div id="app">
<cpn :categories='categories' :items='items' @btnclick='changeitems'></cpn>
</div>
</body>
<template id="cpn">
<div>
<button type="button" class="btn" v-for="(category, index) in categories" @click="btnclick(category, index)" :class="{active: currentIndex == index}">
{
{category.name}}
</button>
<br>
<ul>
<li v-for="item in items">{
{item}}</li>
</ui>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script> const cpn = {
template: '#cpn', props: {
items: {
type: Array, default() {
return ['无数据'] } }, categories: {
type: Array, required: true } }, methods: {
btnclick(category, index){
//console.log(category); this.currentIndex = index; this.$emit('btnclick', category); }, }, data() {
return {
currentIndex: 0 } } }; const app = new Vue({
el: '#app', data: {
categories: [ {
id: 'movies', name: '电影' }, {
id: 'fruits', name: '水果' }, {
id: 'musics', name: '音乐' } ], //ff lists: {
movies: ['星际穿越', '火星救援', '星际迷航', '星球大战'], fruits: ['苹果', '香蕉', '草莓', '鸭梨'], musics: ['生命因你而火热', '没有理想的人不伤心', '你要跳舞吗'] }, items: [] }, components: {
cpn }, mounted() {
//模拟网络请求和数据绑定 this.items = this.lists.movies; }, methods: {
changeitems(category) {
//模拟网络请求和数据绑定 this.items = this.lists[category.id]; } } }) </script>
</html>