一篇文章讲清楚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模板放在了注册组件的内部,这样做会带来两个坏处

  1. 没有代码补全,撰写模板的过程更麻烦
  2. 代码看起来更加凌乱

因此一般我们会采用模板的分离写法,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>


全部评论

相关推荐

评论
3
1
分享

创作者周榜

更多
牛客网
牛客企业服务