此学习教程是对官方教程的解析,
本章节主要涉及到的官方教程地址:
上一章 :Vue入门实战教程(五)—— 控制层:方法及过滤器
本章介绍组件基本知识, 大概了解组件是怎么回事。
组件构造和注册
组件构造
看过vue源码就可以知道Vue组件是VueComponent的实例,VueComponent类继承自Vue类,所以Vue组件本质是Vue类的实例,即Vue 实例。
VueComponent类是Vue组件构造器,通过调用Vue.extend( options )产生。其中构造选项options和Vue实例化的选项是一致的,只不是挂载元素选项el、外部属性赋值选项propsData必须在实例化中才可使用。构造选项和实例化选项都存在时,实例化选项会覆盖构造选项。
例子:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app0">
</div>
<Br/>
<div id="app1">
</div>
<Br/>
<div id="app2">
</div>
<script>
var MsgVueComponent = Vue.extend({
//必须是函数,组件数据必须是独立的拷贝
data: function () {
return {
msg2: 'hello'
}
},
//外部属性
props: ['msg'],
//组件模板
template: '<div>comp.msg:{{ msg }}<Br/>comp.msg2:{{ msg2 }}</div>'
});
//第一个组件, MsgVueComponent的实例, 实例化选项会覆盖MsgVueComponent的构造选项
var comp1 = new MsgVueComponent({
//组件也是Vue实例,可以挂载
el: "#app0",
//为外部属性msg赋值
propsData: {
msg: 'hello0'
},
//覆盖内部属性data
data:{
msg2: 'hi'
},
//覆盖构造器定义的模板
template: '<div>comp0.msg:{{ msg }}<Br/> comp0.msg2:{{ msg2 }}</div>'
})
//第二个组件,一般用法,为外部属性msg赋值就行了。
var comp1 = new MsgVueComponent({
el: "#app1",
propsData: {
msg: 'hello1'
}
})
//第三个组件,创建组件实例并手动挂载
var comp2 = new MsgVueComponent({
propsData: {
msg: 'hello2'
}
})
comp2.$mount("#app2");
</script>
</body>
</html>
组件注册
全局注册
全局注册的组件可以用在其被注册之后的任何 (通过 new Vue) 新创建的 Vue 根实例,也包括其组件树中的所有子组件的模板中。
使用以下API:
Vue.component( id, [definition] )
第一个参数id:组件名称
第二个参数 [definition] :组件构造器,或者组件构造器的构造选项
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app1">
<msg-comp msg="hello1"></msg-comp>
</div>
<script>
var MsgVueComponentOptions = {
//必须是函数,组件数据必须是独立的拷贝
data: function () {
return {
msg2: 'hello'
}
},
//外部属性
props: ['msg'],
//组件模板
template: '<div>comp.msg:{{ msg }}<Br/>comp.msg2:{{ msg2 }}</div>'
}
//第一种全局注册方法,第二个参数使用组件构造器的构造选项对象
Vue.component('msg-comp', MsgVueComponentOptions);
//第二种全局注册方法,第二个参数使用组件构造器
//Vue.component('msg-comp', Vue.extend(MsgVueComponentOptions));
var vm = new Vue({
el: "#app1",
})
</script>
</body>
</html>
局部注册
局部注册的组件仅可以用在其被注册的 Vue 实例中使用,不包括其组件树中的子组件的模板中。
局部注册的方法:
在实例化选项components中设置组件名和组件构造器
{
components: {
//第一种局部注册方法,第二个参数使用组件构造器的构造选项对象
‘msg-comp’: MsgVueComponentOptions
//第二种全局注册方法,第二个参数使用组件构造器
//‘msg-comp’: Vue.extend(MsgVueComponentOptions)
}
}
例子:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app1">
<msg-comp msg="hello1"></msg-comp>
</div>
<script>
var MsgVueComponentOptions = {
//必须是函数,组件数据必须是独立的拷贝
data: function () {
return {
msg2: 'hello'
}
},
//外部属性
props: ['msg'],
//组件模板
template: '<div>comp.msg:{{ msg }}<Br/>comp.msg2:{{ msg2 }}</div>'
}
//局部注册
var vm = new Vue({
el: "#app1",
components: {
//第一种局部注册方法,第二个参数使用组件构造器的构造选项对象
'msg-comp': Vue.extend(MsgVueComponentOptions)
//第二种全局注册方法,第二个参数使用组件构造器
//'msg-comp': Vue.extend(MsgVueComponentOptions)
}
})
</script>
</body>
</html>
组件主要构造选项
我们看一下上面例子组件的构造选项:
var MsgVueComponentOptions = {
//必须是函数,组件数据必须是独立的拷贝
data: function () {
return {
msg2: 'hello'
}
},
//外部属性
props: ['msg'],
//组件模板
template: '<div>comp.msg:{{ msg }}<Br/>comp.msg2:{{ msg2 }}</div>'
}
如上所示,组件构造选项主要有以下三个:
内部属性:data
组件是可复用的,所以组件的内部属性data必须是独立的拷贝,不能相互影响。所以data必须是一个构造函数,返回一个新的data对象,而不是引用一个相同的data对象。
外部属性:props
props是父组件向子组件传递数据的方式,值可以是任何类型。Prop 是你可以在组件上注册的一些自定义 attribute。当一个值传递给一个 prop attribute 的时候,它就变成了那个组件实例的一个 property。
三种传值方式:
以自定义 attribute赋值,非v-bind方式
<msg-comp msg="hello1"></msg-comp>
以自定义 attribute赋值,v-bind方式
<msg-comp v-bind:msg="msg"></msg-comp>
new Vue({ data: { msg: 'hello1' } })
组件实例化时对外部属性赋值
new MsgVueComponent({ propsData: { msg: 'hello1' } })
组件模板:template
模板约束
每个组件必须只有一个根元素。所以如果多个元素的话,模板的内容必须包裹在一个父元素中。
例如以下html
<h3>{{ title }}</h3>
<div v-html="content"></div>
必须转换为
<div class="blog-post">
<h3>{{ title }}</h3>
<div v-html="content"></div>
</div>
插槽
插槽的作用是将组件标签包含的内容嵌入到组件模板中。
例如组件注册如下:
Vue.component('alert-box', {
template: `
<div class="demo-alert-box">
<strong>Error!</strong>
<slot></slot>
</div>
})
组件使用:
<alert-box>
Something bad happened.
</alert-box>
最终渲染成:
<div class="demo-alert-box">
<strong>Error!</strong>
Something bad happened.
</div>
可以看到,最终渲染结果是把组件标签包含的内容Something bad happened.
嵌入到了组件模板<slot></slot>
的位置。
那插槽最大的好处是什么呢?
回顾一下vue官方教程实战(二)——Vue 实例的例子:我们要创建一个包含下面这种树形结构Vue 实例的Vue应用:
根实例
└─ TodoList
├─ TodoItem
└─ TodoListFooter
最终实现了如下效果:
<div id="app-7">
<todo-list></todo-list>
</div>
虽然上面的实现在组件内部实现了树型结构, 但最理想的实现应该是这样的:
<div id="app-7">
<todo-list>
<todo-item v-for="item in groceryList" v-bind:todo="item" v-bind:key="item.id"></todo-item>
<todo-list-footer></todo-list-footer>
</todo-list>
</div>
这在没有插槽slot
是不可能的。现在有了插槽,我们可以重写这个例子:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app-7">
<todo-list>
<todo-item v-for="item in groceryList" v-bind:todo="item" v-bind:key="item.id"></todo-item>
<todo-list-footer></todo-list-footer>
</todo-list>
</div>
<script>
//局部注册todoItem组件
var TodoItem = {
props: ['todo'],
template: '<li>{{ todo.text }}</li>'
}
//局部注册TodoListFooter组件
var TodoListFooter = {
template: '<li>=======</li>'
}
//局部注册TodoListFooter组件
//导入TodoItem、TodoListFooter组件
var TodoList = {
template: '<ol><slot></slot></ol>'
}
//创建名为app7的Vue应用
//导入TodoList组件
var app7 = new Vue({
el: '#app-7',
data: {
groceryList: [
{ id: 0, text: '蔬菜' },
{ id: 1, text: '奶酪' },
{ id: 2, text: '随便其它什么人吃的东西' }
]
},
components: {
'todo-list':TodoList,
'todo-item':TodoItem,
'todo-list-footer': TodoListFooter
}
})
</script>
</body>
</html>
解析 DOM 模板时的注意事项
有些HTML 元素之间有一些严格的父子关系(即父元素内部严格限制可以出现哪些子元素,或子元素只能出现在某些特定的父元素内部)
例如
<ul>、<ol>
对应<li>
<table>
对应<tr>
<select>
对应<option>
如上原因,如果我们从以下来源使用模板的话,会造成渲染错误:
- 使用挂载元素el的inner Html,即只有el,没有template和渲染函数
- 使用template: css选择器, 且不是选择
<script type="text/x-template">
的内容 - 使用渲染函数
比如下例,这个自定义组件<blog-post-row>
会被作为无效的内容提升到外部,并导致最终渲染结果出错
<table>
<blog-post-row></blog-post-row>
</table>
可以全用is
attribute进行转化,意思是这不是普通DOM元素,而是Vue组件。
<table>
<tr is="blog-post-row"></tr>
</table>
如果我们从以下来源使用模板的话,这条限制是不存在的:
- 字符串 (例如:template: ‘…’)
- 单文件组件 (.vue)
<script type="text/x-template">
组件自定义事件
如下例如示:
在组件模板中的其中一个元素上使用$emit
抛出事件
$emit
的第一个参数’enlarge-text’是事件名,对应父元素blog-post上的v-on:enlarge-text
$emit
的第二个参数0.1,对应父元素blog-post上的v-on:enlarge-text中的 $event
这也是子组件向父组件传递数据的一种方式
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="blog-posts-events-demo">
<div :style="{ fontSize: postFontSize + 'em' }">
<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:post="post"
v-on:enlarge-text="postFontSize += $event"
></blog-post>
</div>
</div>
<script>
Vue.component('blog-post', {
props: ['post'],
template: '\
<div class="blog-post">\
<h3>{{ post.title }}</h3>\
<button v-on:click="$emit(\'enlarge-text\', 0.1)">Enlarge text</button>\
<div v-html="post.content"></div>\
</div>\
'
})
new Vue({
el: '#blog-posts-events-demo',
data: {
posts: [
{ id: 0, title: '蔬菜' },
{ id: 1, title: '奶酪' },
{ id: 2, title: '随便其它什么人吃的东西' }
],
postFontSize: 1
}
})
</script>
</body>
</html>
组件切换(动态组件)
比如在一个多标签的界面里, 不同组件之间进行动态切换是非常有用的
基本语法
<!-- 组件会在 `currentTabComponent` 改变时改变 -->
<component v-bind:is="currentTabComponent"></component>
使用已注册组件的名字来切换组件
例子:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="example">
<input type="button" value="A组件" @click="currentTabComponent='a_component'"/>
<input type="button" value="B组件" @click="currentTabComponent='b_component'"/>
<!--创建动态组件 <component :is="组件名称"></component> -->
<component :is="currentTabComponent"></component>
</div>
<script>
new Vue({
el: '#example',
data:{
currentTabComponent:'a_component'
},
components:{
a_component:{
template:'<h1>我是A组件</h1>'
},
b_component:{
template:'<h1>我是B组件</h1>'
}
}
})
</script>
</body>
</html>
使用组件的选项对象来切换组件
和上例同样效果的例子:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="example">
<input type="button" value="A组件" @click="compName='A'"/>
<input type="button" value="B组件" @click="compName='B'"/>
<!--创建动态组件 <component :is="组件名称"></component> -->
<component :is="currentTabComponent"></component>
</div>
<script>
new Vue({
el: '#example',
data:{
compName:'A'
},
computed: {
currentTabComponent: function () {
return {template:'<h1>我是'+this.compName+'组件</h1>'};
}
}
})
</script>
</body>
</html>
本章节教程结束。
全部教程地址:Vue入门实战教程 | 寒于水学习网