目录:
一、什么是组件:
-
在 Vue 里,一个组件本质上是一个拥有预定义选项的一个 Vue 实例。
-
在Vue中,组件是可复用的 Vue 实例,且带有一个名字。
-
组件是构成页面中独立结构单元,组件主要以页面结构形式存在,不同组件也具有基本交互功能。
-
如下图所示,每个图书表项就是一个Vue组件:
二、组件树概念:
通常一个页面会以一棵嵌套的组件树的形式来组织。这个思想很重要。
例如:上图左侧是一个页面,右侧是对应的组件树。当前的页面是一个Vue实例(上图组件树的第一层)。页面中有三个组件:页头、侧边栏、内容区(上图组件树的第二层),这三个组件构成了当前页面。其中侧边栏又由两个小侧边栏组成,内容区由三个小内容框组成(上图组件树第三层)。
三、组件的创建方式:
使用组件前首先需要创建组件。
备注:这里我们留个印象,下面的创建方式都是以局部组件为例,我主要想讲的是使用template模板和不使用template模板的区别。(你要是觉得乱就忽略这句话,等到看完所有内容再回来看这句话)
1.直接创建组件:
<script>
//创建组件com1
var com1 = {
//template中是组件内容,可以是多个html标签潜逃而成,但是根标签只能有一个
template: "
<div>
<p>我是组件com1</p>
<p>哈哈哈</p>
</div>
"
}
</script>
效果大概就是这样的:
可以看出这种创建组件的方式非常乱,所以vue提供了下面这种方式。
2.使用template模板创建组件:
Vue提供了template模板来定义组件内容,可以在该标签中书写HTML代码,然后通过id值绑定到组件内的template属性上,这样就有利于在编辑器中显示代码提示和高亮显示,不仅改善了开发体验,也提高了开发效率。
<script>
//创建组件com1
var com1 = {
//使用id与template模板绑定
template: '#com1template'
}
</script>
<html>
<!--模板必须有id-->
<template id="com1template">
<!--根元素必须唯一-->
<div>
<p>我是组件com1</p>
<p>哈哈哈</p>
</div>
</template>
</html>
四、组件的注册方式及使用:
为了能使用这些组件,组件必须先注册以便 Vue 能够识别。
1.局部注册:
通过Vue实例的components属性
来实现局部注册,注册后只能在该Vue实例中使用,其他Vue实例想使用该组件必须也使用components属性来注册该组件。
<script>
//创建组件com1
var com1 = {
template: '<p>我是组件</p>'
}
//创建Vue实例vm1
var vm1 = new Vue({
el: '#app1',
// 通过components属性在vm1实例中注册局部组件com1,并起名myCompent,这样组件com1就可以在vm1实例中使用了。
components: { my-component: com1 }
})
//创建Vue实例vm2
var vm2 = new Vue({
el: '#app2'
})
</script>
<html>
<!--组件在vm1实例下可以使用-->
<div id="app1">
<!--使用组件com1,注意使用的是注册的名字my-component,而不是对象的名字com1-->
<my-component></my-component><!--正确-->
</div>
<!--组件在其他实例下(例如vm2)不可以使用,因为没有注册,必须先注册-->
<div id="app2">
<my-component></my-component><!--报错-->
</div>
</html>
2.全局注册:
Vue.component()
方法用于全局注册组件,全局注册的组件可以用在其被注册之后的任何 (通过 new Vue) 新创建的 Vue 根实例,也包括其组件树中的所有子组件的模板中。
<script>
//创建组件com1并全局注册
Vue.component(
//这就是组件的名字,即<com1></com1>
'com1',
{
template: '<p>我是组件</p>'
}
)
//创建Vue实例vm1
var vm1 = new Vue({
el: '#app1'
})
//创建Vue实例vm2
var vm2 = new Vue({
el: '#app2'
})
</script>
<html>
<div id="app1">
<!--使用组件com1-->
<com1></com1><!--正确-->
</div>
<div id="app2">
<com1></com1><!--正确-->
</div>
</html>
备注:看完全局注册你可能会有疑问:我没有见过这种组件创建的方式呀?上面讲的组件创建方式和这个不一样呀?这时候你再回去看(三)中的备注可能就会理解了.
五、组件的属性:
上面我们学到了组件的创建、注册与使用方法,学到了使用template属性,接下来我们再看一下组件内部还有什么属性。
- 组件是可复用的 Vue 实例,所以它们与 new Vue 接收相同的选项,例如 data、computed、watch、methods 以及生命周期钩子等。多了template属性,但是没有el属性。
//这里以创建组件并全局注册为例
Vue.component('my-component', {
template:'',
//与Vue实例不同的是,在组件中data必须是一个函数
data: function () {
return {
shuxing: ''
}
},
methods: {
// ... ...
},
computed: {
// ... ...
},
created: function () {
// ... ...
},
props:[]
})
六、组件的命名:
组件有两种命名方式:短横线分隔命名、驼峰命名。
注意:组件命名是在注册的时候命名,使用组件的时候也是使用注册的名字,而不是创建的组件对象的名字。
<script>
//创建组件并全局注册例子:
Vue.component('my-component-name', { /* ... */ })
Vue.component('MyComponentName', { /* ... */ })
//创建组件并局部注册例子:
var com1 = {
/*...*/
}
//创建Vue实例vm1
var vm1 = new Vue({
el: '#app1',
//注册组件并给组件命名:
components: { my-component: com1 }
components: { myComponent: com1 }
})
</script>
不管使用哪种方式注册,使用时都是使用驼峰命名:my-component。
<html>
<my-component></my-component>
</html>
七、组件组件之间数据传递:
1.父组件向子组件传值 - Props:
props用来接收父组件中定义的数据,其值为数组。
(1)静态传值:
<html>
<!--父组件-->
<div class="container" id="app">
<!--子组件-->
<blog-post
title="博文1"
content="西游记"><!--这就是父组件向子组件传的值-->
</blog-post>
<!--子组件-->
<div id="blog-post-demo">
<blog-post
title="博文2"
content="水浒传"><!--这就是父组件向子组件传的值-->
</blog-post>
</div>
</div>
<!--template模板-->
<template id="blog-post-template">
<div>
<p>{{title}}</p>
<p>{{content}}</p>
</div>
</template>
</html>
<script>
//创建组件并全局注册
Vue.component('blog-post', {
//接收父组件传来的数据
props: ['title', 'content'],
template: '#blog-post-template’
})
let app = new Vue({
el: '#app'
})
</script>
(2)动态传值:
使用vue指令将父组件data内的属性值传给子组件。
<html>
<!--父组件-->
<div class="container" id="app">
<!--子组件-->
<blog-post
v-for="post in posts"
v-bind:title="post.title"
v-bind:content="post.content"<!--这就是父组件向子组件传的值-->
</blog-post>
</div>
<!--template模板-->
<template id="blog-post-template">
<div>
<p>{{title}}</p>
<p>{{content}}</p>
</div>
</template>
</html>
<script>
//创建组件并全局注册
Vue.component('blog-post', {
//接收父组件传来的数据
props: ['title', 'content'],
template: '#blog-post-template’
})
let app = new Vue({
el: '#app',
data: {
posts: [
{title: '博文1', content: '西游记'},
{title: '博文2', content: '水浒传'}
]
}
})
</script>
效果:
2.子组件向父组件传值 - $emit:
$emit(“b”)表示向父组件抛出事件b。
<html>
<!--父组件-->
<div class="container" id="app">
<!--子组件-->
<div>
<blog-post
@b="a()"
><!--2.自定义事件b,表示父组件接收到子组件抛出的事件b后会执行a()函数-->
</blog-post>
</div>
</div>
<!--template模板-->
<template id="blog-post-template">
<div>
<button @click="$emit('b')"></button><!--1.点击子组件的按钮会向父组件抛出事件b-->
</div>
</template>
</html>
<script>
//创建组件并全局注册
Vue.component('blog-post', {
//接收父组件传来的数据
props: ['title', 'content'],
template: '#blog-post-template’
})
let app = new Vue({
el: '#app',
methods:{
a(){
alert("你点击了子组件");
}
}
})
</script>
效果:点击子组件的按钮会执行父组件的a()函数。
八、案例:实现图书列表界面:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" >
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<title>Document</title>
<style type="text/css" rel="stylesheet">
.text-secondary {
line-height: 1em;
font-size: 0.8em;
border-bottom: 1em;
}
#table_book{
font: Arial, 微软雅黑;
}
a:hover {
text-decoration: underline solid rgb(23, 82, 192);
}
a:link {
text-decoration: none;
}
.borderprimary{
border:solid 1px blue !important;
/* 这里要加一个 !important表示最大优先级,该样式会覆盖原先的样式*/
}
</style>
</head>
<body>
<!-- container居中 -->
<div id="table_book" class="container">
<div class="row">
<one-book
v-for="(book,index) in books"
:book="book"
@delete="deleteBook(index)"
></one-book><!--监听delete事件,监听到时执行deleteBook方法--->
</div>
</div>
<template id="book_temp">
<div class="col-md-4 g-2 gy-md-3" >
<div class="border" v-bind:class={borderprimary:!deleteCanHide}>
<div class="row" @mouseenter="showDelete" @mouseleave="hideDelete">
<img class="col-4" v-bind:src="book.img_url" width="100%" height="100%">
<div class="col-8">
<p class="fs-5"><a href="book.jd_link" class="fs-5">{{book.title}}</a></p>
<p class="text-secondary">{{book.author}}</p>
<p class="text-secondary">出版社:{{book.publisher}}</p>
<p class="text-secondary">出版日期:{{book.publish_date}}</p>
<p class="text-secondary" style="color:red; width:10;">定价:¥{{book.price}}</p>
<div align="right">
<!-- invisible样式类是bootstrap自带的样式类,控制的是标签的invisible属性:invisible:hidden -->
<input type="button" class="btn btn-primary" v-bind:class={invisible:deleteCanHide} value="删除" @click="$emit('delete')"/><!--点击删除按钮会触发delete事件-->
</div>
</div>
</div>
</div>
</div>
</template>
<script>
Vue.component("one-book",{
template:'#book_temp',
props:['book'],
data(){
return{
deleteCanHide:true,
}
},
methods:{
hideDelete(){
this.deleteCanHide = true
},
showDelete(){
this.deleteCanHide = false
}
}
})
var app = new Vue({
el:'#table_book',
created(){
fetch("/books.json")
.then(function(response){
return response.json();
})
.then(function(response){
app.books = response;
})
},
data:{
books:'',
},
methods:{
deleteBook(index){
this.books.splice(index, 1);//books数组中从index位置开始删除1个数据
}
}
})
</script>
</body>
</html>