15.Vue组件:component

简介

组件 (Component) 是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能。在有些情况下,组件也可以表现为用 is 特性进行了扩展的原生 HTML 元素。
所有的 Vue 组件同时也都是 Vue 的实例,所以可接受相同的选项对象 (除了一些根级特有的选项) 并提供相同的生命周期钩子。
 

全局注册

extend + component

使用Vue.extend配合Vue.component可以完成注册。

//h5
<div id="content">
    <!-- 使用组件时不能用驼峰命名法,使用“-”和小写隔开 -->
    <my-component></my-component>
</div>

//js
var myComponent = Vue.extend({
    template: "<h3>我是extend+component方式的组件</h3>"
});
//第一个参数为组件命名,使用驼峰命名法
Vue.component("myComponent", myComponent);

var vm = new Vue({
    el: "#content"
});

上面的js部分也可以简写为:

Vue.component("myComponent", Vue.extend({
    template: "<h3>我是extend+component方式的组件</h3>"
}));

var vm = new Vue({
    el: "#content"
});

运行:
在这里插入图片描述
 

component

直接使用Vue.component也可以完成注册。

<script type="text/javascript">
    Vue.component("myComponent", {
        template: "<h3>component方式的组件</h3>"
    });

    var vm = new Vue({
        el: "#content"
    });
</script>

运行:
在这里插入图片描述
 

script

定义到type="text/x-template"的script里也可以完成注册。

<body>
    <div id="content">
        <my-component></my-component>
    </div>

    <!-- 注意:使用<script>标签时,type指定为text/x-template,意在告诉浏览器这不是一段js脚本,浏览器在解析HTML文档时会忽略<script>标签内定义的内容。-->
    <!-- 这个注册方法需要定义在 Vue 所属的 DOM 元素外。 -->
    <script type="text/x-template" id="outComponent">
        <h3>这是text/x-template</h3>
    </script>
</body>

<script type="text/javascript">
    Vue.component("myComponent", {
        template: "#outComponent"
    });

    var vm = new Vue({
        el: "#content"
    });
</script>

运行:
在这里插入图片描述
 

局部注册

局部注册的component只能在本vm实例范围使用。

<body>
    <div id="content">
        <my-component></my-component>
    </div>
</body>

<script type="text/javascript">
    var vm = new Vue({
        el: "#content",
        components: {
            myComponent: {
            	template: "<h3>私有component</h3>"
            }
        }
    });
</script>

运行:
在这里插入图片描述
 

组件中的date

组件中的data特性:

  • 必须是一个function,由return返回。
  • 返回值必须是一个对象({})。
//h5
<div id="content">
    <my-component></my-component>
</div>

//js
Vue.component("myComponent", {
    template: "<h3>{{msg}} --- {{num}}</h3>",
    data() {
        let myObj = {
        	msg: "这是一个component里的data",
        	num: 66
        };
        return myObj;
    }
});

var vm = new Vue({
    el: "#content"
});

运行:
在这里插入图片描述
 

案例:计数器模块

思路

建立一个组件,UI方面需要一个+1按钮和一个负责展示的标签。通过data保存数据。通过methods改变数据。

实现

<body>
    <div id="content">
        <count></count>
        <hr>
        <count></count>
        <hr>
        <count></count>
    </div>

    <script type="text/x-template" id="countTemp">
        <div>			
            <button v-on:click="add()">+1</button>
            <p>{{num}}</p>
        </div>
    </script>
</body>

<script type="text/javascript">
    Vue.component("count", {
        template: "#countTemp",
        data() {
            return { num : 6 };
        },
        methods: {
            add() {
            	this.num++;
            }
        }
    });
    
    var vm = new Vue({
        el: "#content"
    });
</script>

运行,每个组件都能分别执行+1(如果想点一个按钮全部+1,只需将{ num : 6 }定义在组件外面):
在这里插入图片描述
 

动态组件

v-bind:is="组件名称"可以动态切换组件,如同使用v-if、v-else。

点击按钮可以切换属性cName的值,再将组件的v-bind:is绑定cName,就达到了动态切换的效果:

	<body>
		<div id="content">
			<!-- 1.使cName变换 -->
			<button v-on:click="cName=='c1'?cName='c2':cName='c1'"></button>
			<!-- 2.v-bind:is='cName' -->
			<component :is="cName"></component>
		</div>

	</body>
	<script>
		Vue.component("c1", {
			template: `
			<h3>
				组件1:<input type='text'>
			</h3>`,
			
			//查看c1生命周期
			beforeCreate() { console.log("c1 beforeCreate")},
			created() {console.log("c1 created")},
			beforeMount() {console.log("c1 beforeMount")},
			mounted() {console.log("c1 mounted")},
			beforeDestroy() {console.log("c1 beforeDestroy")},
			destroyed() {console.log("c1 destroyed")}
		});

		Vue.component("c2", {
			template: `
			<h3>
                           组件2:
				<input type='checkbox'>1
				<input type='checkbox'>2
				<input type='checkbox'>3
			</h3>`,
			
			//查看c2生命周期
			beforeCreate() { console.log("c2 beforeCreate")},
			created() {console.log("c2 created")},
			beforeMount() {console.log("c2 beforeMount")},
			mounted() {console.log("c2 mounted")},
			beforeDestroy() {console.log("c2 beforeDestroy")},
			destroyed() {console.log("c2 destroyed")}
		});

		var vm = new Vue({
			el: '#content',
			data: {
				cName: "c1"
			}
		});
	</script>

运行,切换组件2时过程:

  1. 组件2【beforeCreate–created–beforeMount】
  2. 组件1【beforeDestroy–destroyed】
  3. 组件2【mounted】
    在这里插入图片描述

keep-alive

上面的例子中,我们切换组件以后,会把原组件上的数据销毁,如果希望数据保留,可以用,包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。用法:

    <!-- 缓存动态组件 -->
    <keep-alive>
      <component :is="view"></component>
    </keep-alive>
    
    <!-- 多个条件判断的子组件 -->
    <keep-alive>
      <comp-a v-if="a > 1"></comp-a>
      <comp-b v-else></comp-b>
    </keep-alive>

    <!-- 缓存路由组件 -->
    <!-- 使用keep-alive可以将所有路径匹配到的路由组件都缓存起来,包括路由组件里面的组件 -->
    <keep-alive>
        <router-view></router-view>
    </keep-alive>

如果要保留组件切换时的数据,可以用keep-alive包裹组件,并引入keep-alive中的生命周期函数activated ()和deactivated ()查看效果:

	<body>
		<div id="content">
			<!-- 1.使cName变换 -->
			<button v-on:click="cName=='c1'?cName='c2':cName='c1'"></button>	
			<!-- 3.增加keep-alive -->
			<keep-alive>
				<!-- 2.v-bind:is='cName' -->
				<component :is="cName"></component>
			</keep-alive>
		</div>

	</body>
	<script>
		Vue.component("c1", {
			template: `
			<h3>
				组件1:<input type='text'>
			</h3>`,
			
			//查看c1生命周期
			beforeCreate() { console.log("c1 beforeCreate")},
			created() {console.log("c1 created")},
			beforeMount() {console.log("c1 beforeMount")},
			mounted() {console.log("c1 mounted")},
			beforeDestroy() {console.log("c1 beforeDestroy")},
			destroyed() {console.log("c1 destroyed")},
			//c1增加与<keep-alive>相关的生命周期方法
			activated () {console.log("c1 activated")},
			deactivated () {console.log("c1 deactivated")}
		});

		Vue.component("c2", {
			template: `
			<h3>
				组件2:
				<input type='checkbox'>1
				<input type='checkbox'>2
				<input type='checkbox' v-model='cb3'>3
				<input type='text' v-model='txt'>
			</h3>`,
			data(){
				return {
					cb3:false,
					txt:""
				}
			},
			
			//查看c2生命周期
			beforeCreate() { console.log("c2 beforeCreate")},
			created() {console.log("c2 created")},
			beforeMount() {console.log("c2 beforeMount")},
			mounted() {console.log("c2 mounted")},
			beforeDestroy() {console.log("c2 beforeDestroy")},
			destroyed() {console.log("c2 destroyed")},
			//c2增加与<keep-alive>相关的生命周期方法
			activated () {console.log("c2 activated")},
			deactivated () {console.log("c2 deactivated")}
		});

		var vm = new Vue({
			el: '#content',
			data: {
				cName: "c1"
			}
		});
	</script>

运行:组件1初始化会多执行一个activated () ,切换组件2时过程:

  1. 组件2【beforeCreate–created–beforeMount】
  2. 组件1【deactivated】
  3. 组件2【mounted-activated】

切回组件1:

  1. 组件2【deactivated】
  2. 组件1【activated】
    在这里插入图片描述

include与exclude

我们也可以配置哪些组件要缓存:

  • include:匹配的 路由/组件 会被缓存
  • exclude:匹配的 路由/组件 不会被缓存
    <!-- 逗号分隔字符串 -->
    <keep-alive include="a,b">
      <component :is="view"></component>
    </keep-alive>
    
    <!-- 正则表达式 (使用 `v-bind`) -->
    <keep-alive :include="/a|b/">
      <component :is="view"></component>
    </keep-alive>
    
    <!-- 数组 (使用 `v-bind`) -->
    <keep-alive :include="['a', 'b']">
      <component :is="view"></component>
    </keep-alive>

include与exclude匹配规则:

  • 首先匹配组件的name选项,如果name选项不可用。
  • 则匹配它的局部注册名称。 (父组件 components 选项的键值)
  • 匿名组件,不可匹配。
  • 只能匹配当前被包裹的组件,不能匹配更下面嵌套的子组件。
  • keep-alive不会在函数式组件中正常工作,因为它们没有缓存实例。
  • exclude的优先级大于include。

 
上面的例子假设我们只要缓存【组件1】,那么可以修改为:

<keep-alive include="c1">
    <component :is="cName"></component>
</keep-alive>

运行,只有【组件1】的数据被缓存了:
在这里插入图片描述
 

组件之间传值

props

子组件无法直接访问父组件的方法和属性:

	<body>
		<div id="content">
			<com1></com1>
		</div>
	</body>

	<script type="text/javascript">
		var vm = new Vue({
			el: "#content",
			data: {
				msg: "im outer"
			},
			components:{
				com1:{
					template:"<h3>{{msg}} -- im inner</h3>"
				}
			}
		});
	</script>

运行报错,可见父控件无法通过上面方式给子控件传值:
在这里插入图片描述
 
正确的父控件给子控件传值步骤:

  1. 组件使用 v-bind:XXX 绑定父控件属性名。
  2. 子控件定义props(数组)属性,其中定义“XXX”。
  3. 子控件使用:{{XXX}}。
	<body>
		<div id="content">
			<com1 v-bind:pro="msg"></com1> // 1. v-bind:pro赋值msg
		</div>
	</body>

	<script type="text/javascript">
		var vm = new Vue({
			el: "#content",
			data: {
				msg: "im outer"
			},
			components: {
				com1: {
					template: "<h3>{{pro}} -- im inner</h3>", // 3. {{pro}}调用
					props: ["pro"] // 2. 定义props数组
				}
			}
		});
	</script>

运行:
在这里插入图片描述
 
dataprops的区别:

  • 组件中的data可读写,但props只是可读(虽然可以修改并成功运行,但vue会报错)。
  • data数据是组件自身私有的,但props数据是父控件传来的。
     

$emit

props可用于父控件–>子控件传值,而$emit可用于子控件调用父控件函数,也就实现了子控件–>父控件传值。

	<body>
		<div id="content">
			<!-- 2.使用模板标签的地方,用v-on让父控件的方法和自己命名的方法引用绑定 -->
			<com1 v-on:myfunc="show"></com1>
		</div>
	</body>
	<script type="text/javascript">
		var vm = new Vue({
			el: "#content",
			methods: {
				show() {
					console.log("调用父控件方法");
				}
			},
			components: {
				com1: {
					//1.模板中事件绑定方法
					template: "<button v-on:click='go()'>调用父组件方法</button>",
					methods: {
						go() {
							console.log("调用子控件方法");
							//3.子控件方法中使用this.$emit调用2中的引用名,即可引用父控件方法
							this.$emit("myfunc");
						}
					}
				}
			}
		});
	</script>

运行打印:
调用子控件方法
调用父控件方法
 
如果调用父控件函数的同时需要传递参数

		var vm = new Vue({
			el: "#content",
			methods: {
				//父控件方法支持传参
				show(msg, num) {
					console.log("调用父控件方法, msg:" + msg);
					console.log("调用父控件方法, num:" + num);
				}
			},
			components: {
				com1: {
					template: "<button v-on:click='go()'>调用父组件方法</button>",
					methods: {
						go() {
							console.log("调用子控件方法");
							//子控件$emit()参数引用后追加的参数就是传给父控件方法的参数
							this.$emit("myfunc", "123", 888);
						}
					}
				}
			}
		});

运行打印:
调用子控件方法
调用父控件方法, msg:123
调用父控件方法, num:888
 
同理,可以将子控件data中的值传递到父控件data中。

		var vm = new Vue({
			el: "#content",
			data: {
				//1.父控件中有一个尚未赋值的fObj
				fObj: null
			},
			methods: {
				show(obj) {
					//4.父控件方法中给fObj赋值
					this.fObj = obj;
					console.log("调用父控件方法");
					console.log(`name=${this.fObj.name} age=${this.fObj.age}`);
				}
			},
			components: {
				com1: {
					template: "<button v-on:click='go()'>调用父组件方法</button>",
					//2.子控件初始化了一个sObj
					data() {
						return {
							sObj: {
								name: "tom",
								age: 20
							}
						}
					},
					methods: {
						go() {
							console.log("调用子控件方法");
							//3.sObj通过$emit()传递给父控件
							this.$emit("myfunc", this.sObj);
						}
					}
				}
			}
		});

运行打印:
调用子控件方法
调用父控件方法
name=tom age=20
 

自定义组件的 v-model

一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件,但是像单选框、复选框等类型的输入控件可能会将 value attribute 用于不同的目的。model 选项可以用来避免这样的冲突:

<html>
	<head>
		<meta charset="utf-8" />
		<title></title>
		<script src="js/vue.js"></script>
	</head>
	<body>
		<!-- vue作用范围 -->
		<div id="content">
			<base-checkbox v-model="select"></base-checkbox>
			<div>{{select}}</div>
		</div>

		<!-- 自定义的checkbox组件 -->
		<script type="text/x-template" id="myInputCom">
			<div>
				<!-- 注意v-bind和v-on在做啥-->
				<input type="checkbox" v-bind:checked="checked" v-on:change="$emit('change', $event.target.checked)">
				自定义多选组件
			</div>		
		</script>
	</body>
	<script>
		//声明组定义组件
		Vue.component('base-checkbox', {
			model: {
				prop: 'checked',
				event: 'change'
			},
			props: {
				checked: Boolean
			},
			template: "#myInputCom"
		});

		//声明vue
		var vm = new Vue({
			el: '#content',
			data: {
				select: false
			}
		});
	</script>
</html>

运行,这样我们就为自定的checkbox实现了v-model:
在这里插入图片描述
 

相关资料

Vue 组件中的 data 为什么是函数?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值