【Vue实战教程】之深入了解Vue组件

深入了解Vue组件

1 什么是组件化开发

组件开发开发是Vue.js框架的核心特性之一,也是目前前端技术框架中最常见的一种开发模式。在Vue.js中,组件就是一个可以复用的Vue实例,拥有独一无二的组件名称,可以扩展HTML元素,使用组件名称作为自定义的HTML标签。

在Vue.js的项目中,每个组件都是一个Vue实例,所以组件内的属性选项都是相同的,例如data、computed、watch、methods以及生命周期钩子等。仅有的例外是像el这样根实例特有的选项。

在很多场景下,网页中的某些部分都是可以复用的,例如头部导航、猜你喜欢、热点资讯等等。我们可以将网站中能够重复使用的部分设计成一个个的组件,当需要的时候,直接引用这个组件即可。

Vue组件化开有别与前端传统的模块化开发。模块化是为了实现每个模块、方法的功能单一,一般是通过代码逻辑的角度进行划分;而组件化开发,更多的是实现前端UI的重复使用。

2 Vue自定义组件

使用vue-cli工具创建的项目中,src目录下是用来存放项目源码的,在src目录下会自动创建两个目录,一个是src/views 目录,另一个是src/components 目录。这两个目录都是用来创建组件的,但是为了区分组件的功能,一般在src/views目录下创建的是视图组件,在src/components目录下创建的是公共UI组件。

我们可以在src/views 目录下创建一个Home.vue的视图组件,代码如下:

<template>
  <div>
    这是Home页面!
  </div>
</template>

然后将Home.vue组件引入到项目的根组件App.vue中,代码如下:

<template>
  <div>
	 <!-- 使用标签的方式使用自定义组件 -->
     <Home></Home>
  </div>
</template>

<script>
import Home from './views/Home.vue'
export default {
	components: {
		Home
	}
}
</script>

在App.vue根组件中使用components选项注册自定义组件,完成上面代码后启动项目,在Google Chrome浏览器中运行的效果如图所示。
在这里插入图片描述

2.1 组件的封装

在项目的开发中,很多时候需要某些UI部分封装成一个独立的组件,这部分UI可以是一个页面中的模块,例如商品列表;也可以是一个很小的部件,例如按钮。对于这种常用的UI元素我们可以创建一个组件放到components目录下。

在src/components目录下新建一个Button.vue的文件,代码如下:

<template>
	<button>按钮</button>
</template>

在上面的代码中,标签内只能有一个子节点,如果在标签的同级位置还有其他的标签元素的话,需要在和同级标签的外部再添加一个父节点。
自定义Button.vue组件创建成功后,在App.vue根组件中引入,代码如下:

<template>
  <div>
	 <!-- 使用标签的方式使用自定义组件 -->
     <el-button />
  </div>
</template>
<script>
import Button from './components/Button.vue'
export default {
	components: {
	        'el-button': Button
	}
}
</script>

上面代码中,components选项内使用的 “el-button” 作为自定义组件的别名,在模板中用 作为HTML扩展标签。启动项目,在Google Chrome浏览器中运行的效果如图所示。
在这里插入图片描述

图5.2 自定义按钮组件

2.2 自定义组件上的属性

在上一小节示例代码中,使用 自定义UI组件,就可以在浏览器中渲染出按钮元素,但是在很多时候,自定义组件内部不能直接定义按钮的样式与属性,应该由组件的使用者对按钮组件进行创建并赋予其相关的属性值。我们可以在自定义组件上使用标签属性的方式,为自定义组件内传入参数,并在组件内部渲染即可。
例如,我们可以在创建Button.vue组件时,为其设置props选项属性,代码如下:

<template>
	<button>{{text}}</button>
</template>

<script>
export default {
	props: {
	      text: String
	}
}
</script>

在上面示例代码中,使用props选项属性为Button.vue自定义组件上定义了属性 text,在其他组件内引入该自定义组件时,就可以使用该属性作为标签属性,并为自定义组件传入具体的内容。
在App.vue中引入Button.vue自定义组件,代码如下:

<template>
  <div>
	 <!-- 使用标签的方式使用自定义组件,并通过text属性设置按钮文本内容 -->
     <el-button text="提交" />
  </div>
</template>

<script>
import Button from './components/Button.vue'
export default {
	components: {
	        'el-button': Button
	}
}
</script>

在上面的示例代码中,通过为 标签设置 text 属性的方式设置按钮显示的文本内容,在Google Chrome浏览器中运行的效果如下图所示。
在这里插入图片描述

2.3 自定义组件上的事件

自定义组件与预定义的HTML标签是有区别的,预定义的HTML标签能够被浏览器识别并渲染,所有在浏览器中支持HTML标签上的原始属性和事件,但是自定义组件的标签在浏览器中,渲染的是组件内部定义的UI样式,浏览器无法直接识别自定义组件标签上的属性和事件。

在使用自定义组件时,如果要为组件标签上设置属性和事件,就要在自定义组件的内部提前声明。在组件上设置事件与设置属性还有所差别,设置属性只需要在组件内部使用props选项即可。设置事件,需要考虑事件的执行时机,并在组件内部为原始的HTML标签定义该事件。

例如自定义按钮组件,并为组件上设置点击事件,代码如下:

<template>
	<button @click="handleClick">{{text}}</button>
</template>
<script>
export default {
	props: {
	    text: String
	},
	methods: {
	    handleClick(){
		this.$emit('click')
	    }
	}
}
</script>

上面示例代码,为原生的 标签添加了点击事件,并在点击事件的触发函数中调用了$emit()方法,触发该自定义组件上定义的名为“click”的事件。
在App.vue中引入Button.vue组件,代码如下:

<template>
  <div>
	 <!-- 使用标签的方式使用自定义组件 -->
     <el-button text="提交" @click="submit"/>
  </div>
</template>
<script>
import Button from './components/Button.vue'
export default {
	components: {
	    'el-button': Button
	},
	methods: {
	    submit(){
		console.log('提交按钮被点击了')
	    }
	}
}
</script>

上面示例代码中,为标签定义了@click事件,在自定义组件标签中是没有默认的点击事件的,但是在创建Button.vue组件时,为原生按钮元素添加了点击事件,并使用 $emit(‘click’) 方法触发了click事件,所以在标签上定义@click事件是可以触发的。
在Google Chrome浏览器中运行效果如图所示。
在这里插入图片描述

3 组件属性校验

在自定义组件时,可以在自定义组件文件的内部使用props选项来为组件上自定义一些功能。在使用自定义组件标签时,这些预先定义的props属性可以使用标签属性的书写方式在组件标签上声明,此时,标签上的属性值被传入到自定义组件内的props选项中,在组件内就可以获取到这个值了。
HTML 标签中的属性名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名。代码如下:

Vue.component('blog-post', {
  // 在 JavaScript 中是 camelCase 的
  props: ['postTitle'],
  template: '<h3>{{ postTitle }}</h3>'
})

在 HTML 中的属性名是使用 kebab-case 格式命名的,代码如下:

<blog-post post-title="hello!"></blog-post>

我们可以为组件的 prop 指定验证要求,如果有一个需求没有被满足,则 Vue 会在浏览器控制台中报出警告。在使用prop验证属性的类型时,值的类型要使用对象,不能使用字符串。代码如下:

Vue.component('my-component', {
  props: {
    // 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
    propA: Number,
    // 多个可能的类型
    propB: [String, Number],
    // 必填的字符串
    propC: {
      type: String,
      required: true
    },
    // 带有默认值的数字
    propD: {
      type: Number,
      default: 100
    },
    // 带有默认值的对象
    propE: {
      type: Object,
      // 对象或数组默认值必须从一个工厂函数获取
      default: function () {
        return { message: 'hello' }
      }
    },
    // 自定义验证函数
    propF: {
      validator: function (value) {
        // 这个值必须匹配下列字符串中的一个
        return ['success', 'warning', 'danger'].indexOf(value) !== -1
      }
    }
  }
})

4 组件通信

组件化开发是Vue中的核心概念之一,通过设计具有各自状态的UI组件,然后在由这些组件拼成更加复杂的UI页面,使代码更加简洁、容易维护。创建自定义组件在Vue开发中是非常常见的,在这种开发场景下必定会设计到组件之间的通信。在本小节中将要学习的是如何实现组件之间的数据交互。

4.1 父组件向子组件通信

因为Vue项目是单向数据流,所以只能从父组件将数据传递给子组件。具体传递的方式,可以使用上一小节讲到的props选项。在子组件中的props选项中定义属性,在父组件中就可以向子组件传递值了。

子组件 Son.vue 文件代码如下:

<template>
	<div>
	    子组件,接收父组件传值:{{text}}
	</div>
</template>
<script>
export default {
	props: {
	    text: String
	}
}
</script>

父组件 Father.vue 文件代码如下:

<template>
	<div>
	    <h3>父组件向子组件传值</h3>
	    <son v-bind:text="msg"></son>
	</div>
</template>
<script>
import Son from './Son.vue'
export default {
	components: {
	    'son': Son
	},
	data(){
	    return {
		msg: 'hello world!'
	    }
	}
}
</script>

在Google Chrome 浏览器中运行的效果如图所示。
在这里插入图片描述

4.2 子组件向父组件通信

单向数据流决定了,父组件可以影响子组件的数据,但是反之不行。子组件内数据发生更新后,在父组件中无法直接获取到更新后的数据。要想实现子组件向父组件传递数据,可以在子组件数据发生变化后,触发一个事件方法,然后由这个事件方法告诉父组件数据更新了。在父组件中只需要对这个事件进行监听,当捕获到这个事件运行后,再对父组件的数据进行同步更新即可。

子组件 Son.vue 文件代码如下:

<template>
	<div>
	    子组件,输入新值:
	    <input type="text" v-model="value">
	    <button @click="submit">提交</button>
	</div>
</template>
<script>
export default {
	data(){
	    return {
		value: ''
	    }
	},
	methods: {
	    submit(){
		this.$emit('show',this.value);
	    }
	}
}
</script>

父组件 Father.vue 文件代码如下:

<template>
	<div>
	    <h3>父组件监听子组件数据更新:{{msg}}</h3>
	    <son v-on:show="showMsg"></son>
	</div>
</template>
<script>
import Son from './Son.vue'
export default {
	components: {
	    'son': Son
	},
	data(){
	    return {
		msg: ''
	    }
	},
	methods: {
	    showMsg(msg){
		this.msg = msg
	    }
	}
}
</script>

在上面示例代码中,父组件中使用v-on事件监听器来监听子组件的事件,在子组件中使用 $emit() 去触发当前实例上的事件。在Google Chrome 浏览器中运行的效果如图所示。
在这里插入图片描述

5 插槽

在构建页面时,我们常常会把具有公共特性的部分抽取出来,封装成一个独立的组件,但是在实际使用过程中又会产生一些其他问题,不能完全满足开发的需求。例如,当我们需要在自定义组件内添加一些新的元素时,原来的组件封装的方式就不能够实现。这时,我们就需要使用插槽来分发内容。

5.1 什么是插槽

Vue为了实现组件的内容分发,在组件的相关内容中提供了一套用于组件内容分发的API,也就是插槽。这套API使用 内置组件作为承载分发内容的出口。代码如下:

创建父组件 Demo.vue,代码如下:

<template>
	<div>
	    <h3>父组件中使用插槽</h3>
	    <my-slot>
		<p>这是父组件中添加的元素</p>
	    </my-slot>
	</div>
</template>
<script>
import Myslot from './Myslot.vue'
export default {
	components: {
	    'my-slot': Myslot
	}
}
</script>

创建子组件 Myslot.vue,代码如下:

<template>
	<div>
	    <p>这是子组件内容</p>
	    <slot></slot>
	</div>
</template>

在Google Chrome浏览器中运行的效果如图所示。
在这里插入图片描述

5.2 具名插槽

在Vue2.6中,具名插槽和作用域插槽引入了一个新的统一语法的v-slot指令。它取代了slot和slot-scope特性。
在实际的开发过程中,组件中的插槽不止一个,有时需要多个插槽,代码如下:

<div>
	<div class="header">
	    这是页面头部
	</div>
	<div>
	    这是页面的主体内容
	</div>
	<div>
	    这是页面的底部
	</div>
</div>

针对上面的示例, 元素有一个name属性,这个属性可以用来定义额外的插槽,代码如下:

<div>
	<div class="header">
	    <slot name="header"></slot>
	</div>
	<div>
	    <slot></slot>
	</div>
	<div>
	    <slot name="footer"></slot>
	</div>
</div>

当 元素上没有定义name属性时, 出口会带有隐含的 default。在向具名插槽提供内容的时候,可以在一个 元素上使用 v-slot 指令,并以 v-slot 的参数的形式提供其名称。代码如下:

<base-layout>
	<template v-slot:header>
	    这是页面头部
	</template>
	<p>这是页面主体部分</p>
	<template v-slot:footer>
	    这是页面底部
	</template>
</base-layout>

上面代码中,所有的内容都被传入对应的插槽,没有使用带有v-slot的中的内容会被视为默认插槽的内容。
v-slot指令与v-on、v-bind类似,也有自己的缩写形式,把v-slot替换为字符 # 即可。代码如下:

<base-layout>
	<template #header>
	    这是页面头部
	</template>
	<p>这是页面主体部分</p>
	<template #footer>
	    这是页面底部
	</template>
</base-layout>
5.3 作用域插槽

在使用插槽时,经常会有这样的应用场景,在父组件中定义的数据需要在子组件中也能够访问到。例如,在父组件中获取的商品列表数据,当父组件中引入商品卡片的组件时,需要在商品卡片组件内部使用插槽,并把商品数据传给子组件中渲染。
例如,创建商品卡片组件 card.vue 文件,代码如下:

<template>
	<div>
	    <slot>{{ goods.title }}</slot>
	</div>
</template>

在商品卡片组件中想要显示商品的标题,但是商品数据是在父组件中获取到的,所有只有在父组件中使用 组件标签才可以访问到 goods 对象。为了让 goods 在父级的插槽内容可用,可以将 goods 作为 元素的一个属性绑定上去。
card.vue文件代码如下:

<template>
	<div>
	    <slot v-bind:goods="goods">
		{{ goods.title }}
	    </slot>
	</div>
</template>

绑定在 元素上的属性被被称为插槽prop。现在,在父级作用域中,可以给v-slot带一个值,来定义提供的插槽prop的名字。代码如下:

<div>
<card>
	   <template v-slot:default="slotProps">
		{{ slotProps.goods.title }}
	   </template>
</card>
</div>
  • 20
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柯晓楠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值