组件
目标:
- 掌握Vue组件的定义
- 掌握动态组件和父子组件的应用
- 掌握组件间的通信方式
组件概述:
-
组件是什么?
Vue组件是Vue.js最强大的功能之一,用于扩展HTML元素,封装可重用的代码。
-
组件的创建
- 通过构造器创建组件
- 直接创建(常用)
- 组件命名必须全部使用小写
例子:
- 组件的类型
- 全局组件
(1)在Vue实例外部创建
(2)可以在任意Vue实例挂载的DOM元素中使用
例子:
- 局部组件
(1)在Vue实例内部,通过components选项创建
(2)自能在创建组件的实例所挂载的DOM标签中使用
例子:
- 组件的内部构成
- 组件内部包含项
(1)模板:template
(2)数据:data
(必须通过函数的方式定义)
例子:
其他写法:(上面的可以写成独立的)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.0/vue.js"></script>
<title>组建的结构</title>
</head>
<body>
<div id="app">
<mycomp></mycomp>
</div>
<script>
//独立
let mycomp = {
//多个组件必须有一个大的盒子包裹
template: `<div>
<h1>{{title}}</h1>
<button @click="showMsg">更改</button>
</div>`,
data() {
return {
title: '组件的标题',
message: '组件的信息描述'
}
},
methods: {
showMsg() {
this.title = this.message
}}
}
var app = new Vue({
el: '#app',
data: {
title: '实例对象的标题'
},
components: {
'mycomp': mycomp
},
data: {},
methods: {},
})
</script>
</body>
</html>
(3)方法:methods
(4)…
- 引用模板
- 通过template标签在页面中定义组件的内部结构
- 在组件内部的template选项中通过模板标签的id引用
- 模板要求只能有一个根元素
例子:
Vue组件小结:
-
组件作用:自定义标签
-
语法:
(1)组件名(小写)
(2)组件对象:
a. template => 引用模板id => <template></template>
b. data => 函数方式定义
其他…components:{ 组件名:组件对象 => 单独定义 }
-
实例DOM里面使用组件<组件名></组件名>
A. <template>
B.组件对象
C.components
D.<组件名><组件名>
高级组件
- 动态组件
- 通过component标签实现动态改变的组件
例子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.0/vue.js"></script>
<title>组建的结构</title>
</head>
<style>
header {
width: 100%;
height: 100px;
background-color: blue;
}
main {
width: 100%;
height: 400px;
background-color: rgb(24, 214, 56);
}
</style>
<body>
<template id="mycomp">
<!-- 必须有容器div -->
<div>
<header><h1>{{title}}</h1></header>
</div>
</template>
<template id="mycomp01">
<!-- 必须有容器div -->
<div>
<main> <p>{{title}}</p></main>
</div>
</template>
<div id="app">
<component :is="compName"></component>
<button @click="compName='mycomps'">切换</button>
</div>
<script>
let mycomp = {
template: '#mycomp',
data() {
return {
title: '组件的标题',
}
},
}
let mycomp01 = {
template: '#mycomp01',
data() {
return {
title: '组件的标题01',
}
},
}
var app = new Vue({
el: '#app',
data: {
},
components: {
'mycomp': mycomp,
'mycomps': mycomp01,
},
data: {
compName: 'mycomp'
},
})
</script>
</body>
</html>
运行结果:
- keep-alive标签可以保存动态加载组件第一次的状态
例子:(添加随机数random() )
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.0/vue.js"></script>
<title>组建的结构</title>
</head>
<style>
header {
width: 100%;
height: 100px;
background-color: blue;
}
main {
width: 100%;
height: 100px;
background-color: rgb(24, 214, 56);
}
</style>
<body>
<template id="mycomp">
<!-- 必须有容器div -->
<div>
<header><h1>{{title}}</h1>
<p>{{num}}</p></header>
</div>
</template>
<template id="mycomp01">
<!-- 必须有容器div -->
<div>
<main> <p>{{title}}</p>
<p>{{num}}</p></main>
</div>
</template>
<div id="app">
<component :is="compName"></component>
<button @click="compName='mycomps'">切换到下一页</button>
<button @click="compName='mycomp'">返回上一页</button>
</div>
<script>
let mycomp = {
template: '#mycomp',
data() {
return {
title: '组件的标题',
num: Math.random()
}
},
}
let mycomp01 = {
template: '#mycomp01',
data() {
return {
title: '组件的标题01',
num: Math.random()
}
},
}
var app = new Vue({
el: '#app',
data: {
},
components: {
'mycomp': mycomp,
'mycomps': mycomp01,
},
data: {
compName: 'mycomp'
},
})
</script>
</body>
</html>
运行结果:
(每次切换后 ,随机数都不相同)
解决方法:
练习:
使用动态组件实现以下Tab选项切换效果:
- 父子组件
- 父子组件是存在嵌套关系的组件
例子:
-
组件定义时的嵌套
-
组件使用时的嵌套
例子:(父子组件的定义)
运行结果:
代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.0/vue.js"></script>
<title>父子组件的定义</title>
</head>
<body>
<!-- A.创建子组件模板 -->
<template id="son">
<div>
<h2>{{title}}</h2>
</div>
</template>
<!--1. 定义父组件模板 -->
<template id="father">
<div>
<h1>{{title}}</h1>
<!-- D.使用父组件 -->
<mysons></mysons>
</div>
</template>
<div id="app">
<!-- 4.使用父组件 -->
<myfathers></myfathers>
</div>
<script>
// B.创建子组件对象
let sons = {
template: '#son',
data() {
return {
title: '子组件的标题'
}
}
}
// 2.创建父组件对象
let fathers = {
template: '#father',
data() {
return {
title: '父组件的标题'
}
},
// C.定义子组件
components: {
'mysons': sons
}
}
var app = new Vue({
el: '#app',
data: {},
// 3.定义父组件
components: {
'myfathers': fathers,
}
})
</script>
</body>
</html>
- 子组件获取父组件的数据
-
在子组件中通过props选项定义属性
-
在父组件中,把父组件的数据和子组件的属性进行绑定
-
子组件模板中使用父组件数据
例子:
运行结果:
代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.0/vue.js"></script>
<title>父组件传参到子组件</title>
</head>
<body>
<!-- A.创建子组件模板 -->
<template id="son">
<div>
<h2>{{title}}</h2>
<!-- (3).使用父组件数据 -->
<div style="width: 100; height: 100px; border: 1px solid red;">
{{son_message}}</div>
</div>
</template>
<!--1. 定义父组件模板 -->
<template id="father">
<div>
<h1>{{title}}</h1>
<!-- D.使用父组件 -->
<!-- (2).绑定数据 -->
<mysons :son_message="message"></mysons>
</div>
</template>
<div id="app">
<!-- 4.使用父组件 -->
<myfathers></myfathers>
</div>
<script>
// B.创建子组件对象
let sons = {
template: '#son',
data() {
return {
title: '子组件的标题'
}
},
// (1).定义子组件接收参数的属性名
props: ['son_message']
}
// 2.创建父组件对象son_message
let fathers = {
template: '#father',
data() {
return {
title: '父组件的标题',
message: '这是父组件的内容!'
} },
// C.定义子组件
components: {
'mysons': sons
} }
var app = new Vue({
el: '#app',
data: {},
// 3.定义父组件
components: {
'myfathers': fathers,
}
})
</script>
</body>
</html>
- 父组件获取子组件数据
-
子组件把数据通过事件方式发送出去,事件名必须小写
-
父组件中,注册子组件的事件,并添加事件处理方法
-
在事件处理方法中获取子组件的数据
例子:
运行结果:
代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.0/vue.js"></script>
<title>子组件传参到父组件</title>
</head>
<body>
<!-- A.创建子组件模板 -->
<template id="son">
<div>
<h2>{{title}}</h2>
<button @click="sendMsg">发送数据</button>
</div>
</template>
<!--1. 定义父组件模板 -->
<template id="father">
<div>
<h1>{{title}}</h1>
<!-- D.使用父组件 -->
<!-- 二.注册子组件的事情,并确定后续处理方法 -->
<mysons @e_send='reserve'></mysons>
</div>
</template>
<div id="app">
<!-- 4.使用父组件 -->
<myfathers></myfathers>
</div>
<script>
// B.创建子组件对象
let sons = {
template: '#son',
data() {
return {
title: '子组件的标题',
message: '这是子组件定义的内容'
}
},
methods: {
// 一.通过事件方式发送数据
sendMsg() {
this.$emit('e_send', this.message)
}
}
}
// 2.创建父组件对象
let fathers = {
template: '#father',
data() {
return {
title: '父组件的标题'
}
},
// 三.获取参数
methods: {
reserve(result) {
console.log('result:' + result)
}
},
// C.定义子组件
components: {
'mysons': sons
},
}
var app = new Vue({
el: '#app',
data: {},
// 3.定义父组件
components: {
'myfathers': fathers,
}
})
</script>
</body>
</html>
- 单向数据流
- 父组件的数据变化时,会影响到子组件数据
例子:(单向数据流)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>单向数据流</title>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.8/vue.js"></script>
</head>
<script>
// A.定义子组件
let son = {
template: '#son',
data() {
return {
title: '子组件'
}
},
//一、定义子组件的接收参数的属性名
props: ['son_message']
}
//2.创建父组件对象
let father = {
template: '#father',
data() {
return {
title: '父组件',
message: '这是父组件的内容!'
}
},
//C.子组件的定义
components: {
'son': son
}
}
window.onload = function() {
let vm = new Vue({
el: '#mydiv',
data: {},
//3.定义父组件
components: {
'father': father,
}
})
}
</script>
<body>
<template id="son">
<div>
<h1>{{title}}</h1>
<div style="width: 200px;height: 100px;border: 2px solid #cccccc;">
{{son_message}}
</div>
</div>
</template>
<!-- 1.定义父组件模板 -->
<template id="father">
<div>
<h1>{{title}}</h1>
<!-- D.使用子组件 -->
<!-- 二、父组件的属性绑定 -->
<son :son_message='message'></son>
<button @click="message='This is father centent'">修改数据</button>
</div>
</template>
<div id="mydiv">
<!-- 4.使用父组件 -->
<father></father>
</div>
</body>
</html>
运行结果:
2. 绑定的子组件数据变化时,不会影响到父组件的数据,且不能直接修改父组件的数据
例子:(单向数据流的反操作)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.0/vue.js"></script>
<title>单项数据流_反操作</title>
<script>
//B.创建父组件对象
let son = {
template: '#son',
data() {
return {
title: '子组件的标题',
message: '这是子组件内部的数据'
}
},
methods: {
// 一、通过事件方式发送数据
sendMsg() {
this.$emit('e_send', this.message)
},
editMsg() {
this.message = "this is son messges"
}
}
}
//2.创建父组件对象
let father = {
template: '#father',
data() {
return {
title: '父组件的标题',
info: ''
}
},
methods: {
// 三、使用子组件传入数据
receive(result) {
// console.log('result', result)
this.info = result
}
},
// C.子组件的定义
components: {
'son': son
}
}
window.onload = function() {
let vm1 = new Vue({
el: '#mydiv',
data: {},
//3.定义父组件
components: {
'father': father,
}
})
}
</script>
</head>
<body>
<!-- A.定义子组件模板 -->
<template id="son">
<div>
<h1>{{title}}</h1>
<button @click="sendMsg">发送数据</button>
<button @click="editMsg">修改数据</button>
</div>
</template>
<!-- 1.定义父组件模板 -->
<template id="father">
<div>
<!-- 子组件传入数据 -->
<h1>{{title}}</h1>
<p>子组件传入数据:{{info}}</p>
<!-- D.使用子组件 -->
<!-- 二、注册子组件的事件,并确定后续处理 -->
<son @e_send='receive'></son>
</div>
</template>
<div id="mydiv">
<!-- 4.使用父组件 -->
<father></father>
</div>
</body>
</html>
运行结果:
- 单向数据流的解决方案
- 使用事件触发和sync修饰符实现修改父元素的数据
例子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>解决单向数据流方案(使用事件触发)</title>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.8/vue.js"></script>
</head>
<script>
// A.定义子组件
let son = {
template: '#son',
data() {
return {
title: '子组件'
}
},
//一、定义子组件的接收参数的属性名
props: ['son_message'],
methods: {
editData() {
// this.son_message = 'hello'
//不能直接修改绑定父组件传入的数据
//*******解决单项数据流第一步
this.$emit('e_edit', 'hello')
}
},
}
//2.创建父组件对象
let father = {
template: '#father',
data() {
return {
title: '父组件',
message: '这是父组件的内容!'
}
},
//C.子组件的定义
components: {
'son': son
},
methods: {
modify(result) {
this.message = result
}
}
}
window.onload = function() {
let vm = new Vue({
el: '#mydiv',
data: {},
//3.定义父组件
components: {
'father': father,
}
})
}
</script>
<body>
<template id="son">
<div>
<h1>{{title}}</h1>
<div style="width: 200px;height: 100px;border: 2px solid #cccccc;">
{{son_message}}
</div>
<button @click ='editData'>修改父元素数据</button>
</div>
</template>
<!-- 1.定义父组件模板 -->
<template id="father">
<div>
<h1>{{title}}</h1>
<p>{{message}}</p>
<!-- D.使用子组件 -->
<!-- 二、父组件的属性绑定 -->
<son :son_message='message' @e_edit='modify'></son>
<button @click="message='这是父组件的新内容This is father centent'">修改数据</button>
</div>
</template>
<div id="mydiv">
<!-- 4.使用父组件 -->
<father></father>
</div>
</body>
</html>
显示结果:
- 使用面向对象的特性实现修改父元素的数据
例子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>解决单向数据流--面向对象方式</title>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.8/vue.js"></script>
</head>
<script>
// A.定义子组件
let son = {
template: '#son',
data() {
return {
title: '子组件'
}
},
//一、定义子组件的接收参数的属性名
props: ['son_message'],
methods: {
edit() {
this.son_message.content = 'hello'
}
}
}
//2.创建父组件对象
let father = {
template: '#father',
data() {
return {
title: '父组件',
// message: '这是父组件的内容!',
message: {
content: '这是父组件内容'
}
}
},
//C.子组件的定义
components: {
'son': son
}
}
window.onload = function() {
let vm = new Vue({
el: '#mydiv',
data: {},
//3.定义父组件
components: {
'father': father,
}
})
}
</script>
<body>
<template id="son">
<div>
<h1>{{title}}</h1>
<div style="width: 200px;height: 100px;border: 2px solid #cccccc;">{{son_message.content}}</div>
<button @click='edit'>修改父元素数据</button>
</div>
</template>
<!-- 1.定义父组件模板 -->
<template id="father">
<div>
<h1>{{title}}</h1>
<p>{{message.content}}</p>
<!-- D.使用子组件 -->
<!-- 二、父组件的属性绑定 -->
<son :son_message='message'></son>
<button @click="message.content='这是父组件的新内容This is father centent'">修改数据</button>
</div>
</template>
<div id="mydiv">
<!-- 4.使用父组件 -->
<father></father>
</div>
</body>
</html>
运行结果:
练习:
- 创建一个商品数据展示组件,其中包含一个搜索子组件(包含一个文本框和一个按钮),要求在搜索子组件中完成数据搜索功能,然后在商品展示组件中显示搜索到的数据,默认显示全部数据
- 搜索子组件:接受输入查询关键字,查询数据
- 展示组件:显示数据
例子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>商品搜索</title>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.8/vue.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
<script>
window.onload = function() {
let search = {
template: '#search',
data() {
return {
searchKey: '',
datas: []
}
},
mounted() {
$.getJSON('productsAll.json', (result) => {
this.datas = result.data
//把子组件的数据发送
this.$emit('e_getdatas', this.datas)
})
},
methods: {
query() {
let queryData = []
for (let i = 0; i < this.datas.length; i++) {
if (this.datas[i].brand == this.searchKey) {
queryData.push(this.datas[i])
}
}
this.$emit('e_getdatas', queryData)
}
}
}
let productlist = {
template: '#productList',
data() {
return {
products: []
}
},
components: {
'search': search
},
methods: {
receice(datas) {
this.products = datas
}
}
}
let vm1 = new Vue({
el: '#mydiv',
data: {},
components: {
'productlist': productlist
}
})
}
</script>
</head>
<body>
<template id="search">
<div>
<input type="text" name="" id="" v-model='searchKey'>
<button @click="query">搜索</button>
</div>
</template>
<template id="productList">
<div>
<search @e_getdatas='receice'></search>
<table>
<tr>
<th>商品图片</th>
<th>商品描述</th>
<th>品牌</th>
<th>价格</th>
</tr>
<tr v-for ='product in products'>
<td><img :src='product.imgPath' alt=""></td>
<td>{{product.title}}</td>
<td>{{product.brand}}</td>
<td>{{product.price}}</td>
</tr>
</table>
</div>
<!-- 搜索子组件 -->
</template>
<div id="mydiv">
<productlist></productlist>
</div>
</body>
</html>
- 非父子组件间的通信
- 通过空的Vue实例对象创建中央事件的总线,实现事件的监听和触发,完成数据的传输。
- 事件总线对象的创建必须在Vue实例之前完成
例子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>动态组件</title>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.8/vue.js"></script>
<script>
window.onload = function() {
//创建中央事件总线
let eventBus = new Vue({
})
let header = {
template: '#header',
data() {
return {
title: '组件的标题页头',
userName: 'tom'
}
},
methods: {
send() {
// 兄弟组件发送数据
eventBus.$emit('e_send', this.userName)
}
}
}
let main = {
template: '#main',
data() {
return {
title: '组件的标题内容',
num: Math.random()
}
},
// 被挂载就开始监听
mounted() {
eventBus.$on('e_send', (val) => {
alert(val)
})
}
}
let vm1 = new Vue({
el: '#mydiv',
data: {
title: '实例对象的标题',
compName: 'myheader'
},
components: {
'myheader': header,
'mymain': main,
},
methods: {},
})
}
</script>
</head>
<body>
<template id="header">
<div>
<div style="width: 1190px;height: 100px;margin: 0 auto;background-color: cadetblue;">
<h1>xxx网站页头</h1>
<span>欢迎你{{userName}}</span>
</div>
<button @click="send">传参</button>
</div>
</template>
<template id="main">
<div>
<div style="width: 1190px;height: 400px;margin: 0 auto;background-color: rgb(13, 125, 129);">
<h1>xxx网站内容</h1>
<p>{{num}}</p>
</div>
</div>
</template>
<div id="mydiv">
<myheader></myheader>
<mymain></mymain>
</div>
</body>
</html>
-Vue组件slot
- 父子组件混合使用时,slot用于实现组件的内容分发
- 在子组件的内部中添加slot标签,在父组件模板中添加内容,该内容将替换slot标签。
- slot类型:
(1)匿名slot
(2)具名slot
(3)作用域slot
例子:(匿名)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.0/vue.js"></script>
<title>匿名slot</title>
<script>
window.onload = function() {
let mycomp = {
template: '#mycomp'
}
let vm1 = new Vue({
el: '#mydiv',
data: {},
//3.定义父组件
components: {
'mycomp': mycomp,
}
})
}
</script>
</head>
<body>
<template id="mycomp">
<div>
<!-- slot组件不存在时,组件模板会覆盖组件标签内部的内容 -->
<!-- slot存在时,组件标签内部的内容会替换slot标签 -->
<slot></slot>
<h1>我是组件的标题</h1>
</div>
</template>
<div id="mydiv">
<mycomp>
<h1>父级容器提供的标题</h1>
<hp>父级容器提供的内容</p>
</mycomp>
</div>
</body>
</html>
运行结果:
例子:(具名slot)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.0/vue.js"></script>
<title>slot具名</title>
<script>
window.onload = function() {
let mycomp = {
template: '#mycomp'
}
let vm1 = new Vue({
el: '#mydiv',
data: {},
//3.定义父组件
components: {
'mycomp': mycomp,
}
})
}
</script>
</head>
<body>
<template id="mycomp">
<div>
<!-- slot组件不存在时,组件模板会覆盖组件标签内部的内容 -->
<!-- slot存在时,组件标签内部的内容会替换slot标签 -->
<slot name='header'></slot>
<h1>我是组件的标题</h1>
<slot name='footer'></slot>
<slot></slot>
</div>
</template>
<div id="mydiv">
<mycomp>
<h1 slot="header">父级容器提供的标题</h1>
<p slot="footer">父级容器提供的内容</p>
<ul>
<li>首页</li>
<li>商品</li>
<li>个人中心</li>
</ul>
</mycomp>
</div>
</body>
</html>
- slot类型:
- 匿名slot
- 具名slot
- 作用域slot