简介
组件 (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时过程:
- 组件2【beforeCreate–created–beforeMount】
- 组件1【beforeDestroy–destroyed】
- 组件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时过程:
- 组件2【beforeCreate–created–beforeMount】
- 组件1【deactivated】
- 组件2【mounted-activated】
切回组件1:
- 组件2【deactivated】
- 组件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>
运行报错,可见父控件无法通过上面方式给子控件传值:
正确的父控件给子控件传值步骤:
- 组件使用 v-bind:XXX 绑定父控件属性名。
- 子控件定义props(数组)属性,其中定义“XXX”。
- 子控件使用:{{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>
运行:
data和props的区别:
- 组件中的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: