组件-为什么你需要组件
问题导入
下面的代码是一个折叠面板的效果。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- <script src="./vue.js"></script> -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<style>
body{
background-color: #ccc;
}
#app{
width: 400px;
margin: 20px auto;
background-color: #fff;
border:4px solid blueviolet;
border-radius: 1em;
box-shadow: 3px 3px 3px rgba(0, 0, 0, .5);
padding:1em 2em 2em;
}
h3{
text-align: center;
}
.title{
display: flex;
justify-content: space-between;
align-items: center;
border:1px solid #ccc;
padding: 0 1em;
}
.title h4{
line-height: 2;margin:0;}
.container{
border:1px solid #ccc;
padding: 0 1em;
}
.btn {
/* 鼠标改成手的形状 */
cursor: pointer;
}
</style>
</head>
<body>
<div id="app">
<h3>案例:折叠面板</h3>
<div>
<div class="title">
<h4>芙蓉楼送辛渐</h4>
<span class="btn"
v-on:click="isVisiable= !isVisiable">
{
{isVisiable ? '收起' : '展开'}}
</span>
</div>
<div class="container" v-show="isVisiable">
寒雨连江夜入吴,
<br>
平明送客楚山孤。
<br>
洛阳亲友如相问,
<br>
一片冰心在玉壶。
<br>
</div>
</div>
</div>
<script>
const vm = new Vue({
el: "#app",
data: {
isVisiable: true // 表示当前 内容是否显示
}
})
</script>
</body>
</html>
问:如何实现多个折叠面板
解决方案:
- 复制代码
- 代码重复 冗余
- 不利于维护
- 封装组件
感觉好像是封装函数的意思。
体验组件的使用
代码如下:
<div id="app">
<h2>案例:折叠面板</h2>
<pannel></pannel>
<pannel></pannel>
</div>
</div>
<script src="./vue.js"></script>
<script>
var vm = new Vue({
el: '#app',
components: {
pannel: {
data(){
return {
isOpen: false}
},
template: `<div class="box">
<div class="title" >
<h4>我是标题</h4><span class="btn" @click="isOpen=!isOpen">{
{isOpen ? '收起' : '展开'}} </span>
</div>
<div class="container" v-show="isOpen">
寒雨连江夜入吴,
<br>
平明送客楚山孤。
<br>
洛阳亲友如相问,
<br>
一片冰心在玉壶。
</div>
</div>`
}
}
})
</script>
用vue-devtools 来观察上的组件
通过浏览器打开使用了vue技术的网页
组件的定义及使用
组件:一段封装好的,独立的功能代码。
定义格式
全局组件
Vue.component('组件名1',{
template: `` // 字符串,约定模板
data:function (){
return {
// 数据
}
},
computed:{
},
methods: {
},
filter:{
},
watch:{
},
created() {
},
// ...省略其它配置项
})
局部组件: 在 Vue实例中,通过components来定义局部组件
new Vue({
el: '#app'
data: {
},
components: {
// 组件本质上是一个vue实例,但是写法和vue实例不同
组件名1: {
data () {
return {
数据项}}, // 必须是一个函数
template: `<div>模板</div>`, // 约定模板
props:[], // 从父组件中获取数据
methods:{
},
watcher:{
},
filters:{
},
directives:{
},
computed:{
},
created(){
},
components:{
} // 继续定义子组件
// ...省略其它配置项
},
组件名2: {
data () {
return {
数据项}},
template: `<div>模板</div>`,
props:[], // 从父组件中获取数据
methods:{
}
// ...省略其它配置项
}
}
})
组件本质上是一个vue实例,但是在写法上和vue实例稍有不同:
- 不包含 el 选项,el是指定vue实例(或者称为根组件)管理的视图容器。
- 通过 template选项,指定html结构容器,每个组件模板有且只有一个根元素。
- data数据项必须是一个函数,这个函数必须返回一个对象,在这个对象中设置数据项。
使用格式
在模板中使用,就像使用dom标签一样(类似于自定义的标签)。
<组件名1></组件名1>
注意:务必要放在vue的模板中
基本示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- <script src="./vue.js"></script> -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<style>
body{
background-color: #ccc;
}
#app{
width: 400px;
margin: 20px auto;
background-color: #fff;
border:4px solid blueviolet;
border-radius: 1em;
box-shadow: 3px 3px 3px rgba(0, 0, 0, .5);
padding:1em 2em 2em;
}
h3{
text-align: center;
}
.title{
display: flex;
justify-content: space-between;
align-items: center;
border:1px solid #ccc;
padding: 0 1em;
}
.title h4{
line-height: 2;margin:0;}
.container{
border:1px solid #ccc;
padding: 0 1em;
}
.btn {
/* 鼠标改成手的形状 */
cursor: pointer;
}
</style>
</head>
<body>
<div id="app">
<my-com1></my-com1>
<my-com-test></my-com-test>
</div>
</div>
<script>
// 1.定义组件 Vue.component
// 组件的名字:推荐大驼峰法
Vue.component('MyCom1', {
template: `
<div>
<h1>MyCom1组件</h1>
<span>100</span>
</div>` // 指定组件的模板
})
Vue.component('MyComTest', {
template: "<div> <h1>MyComTest</h1></div>" // 指定组件的模板
})
// 2. 使用组件
// 注意:组件名用烤串写法!
// 直接在模板中使用,就像使用自定义标签一样
new Vue({
el: '#app'
})
</script>
</body>
</html>
组件名:
- 在定义组件时,组件名推荐是大驼峰的写法(MyCom1)
- 在模板中使用组件时,全采用全小写的烤串写法(my-com1)
组件的内容由template来决定,可以用模板字符串
组件-是封闭的,独立的
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- <script src="./vue.js"></script> -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<style>
body{
background-color: #ccc;
}
#app{
width: 400px;
margin: 20px auto;
background-color: #fff;
border:4px solid blueviolet;
border-radius: 1em;
box-shadow: 3px 3px 3px rgba(0, 0, 0, .5);
padding:1em 2em 2em;
}
h3{
text-align: center;
}
.title{
display: flex;
justify-content: space-between;
align-items: center;
border:1px solid #ccc;
padding: 0 1em;
}
.title h4{
line-height: 2;margin:0;}
.container{
border:1px solid #ccc;
padding: 0 1em;
}
.btn {
/* 鼠标改成手的形状 */
cursor: pointer;
}
</style>
</head>
<body>
<div id="app">
<my-com1></my-com1>
<hr>
<my-com1></my-com1>
</div>
</div>
<script>
// 定义一个组件:
// 组件名(大驼峰), 配置对象
Vue.component('MyCom1', {
template: `
<div>
mycom1- {
{num}} <button @click="hClick">+1</button>
<p>{
{ cDouble }}</p>
</div>
`,
// 格式要求: 组件中的data是一个函数,这个函数返回一个对象
data () {
return {
// 在组件内部定义的数据项
num: 100
}
},
computed: {
cDouble () {
return 2 * this.num
}
},
methods: {
hClick () {
this.num++
}
}
})
// 使用组件时,应该在vue实例的模板中使用
new Vue({
el: '#app',
// Vue实例中的data就是一个对象
data: {
}
})
</script>
</body>
</html>
注意:组件内部的data定义时,必须要是一个函数!
组件-如何理解组件
组件是什么?
- 组件化开发是vue的重要特性之一。一个页面是由不同的组件构成的。
- 组件可以理解是可复用的 Vue 实例,它可以包含独立的HTML结构,CSS样式(会在后面的单文件组件中讲到),JS代码。它可以把一个完整的页面,根据界面功能拆分成若干组件。
- 组件是自定义标签。
组件与vue实例的关系
- 组件本质上是一个vue实例,但是写法和vue实例不同
new Vue({//配置对象})
Vue.component('组件名', { // 配置对象 })
- 组件有配置对象:
- 不包含 el 选项,el是指定vue实例(或者称为根组件)管理的视图容器。
- 通过 template选项,指定html结构容器,每个组件模板有且只有一个根元素。
- data数据项必须是一个函数,通过它的返回值来设置数据。
- 配置选项:data methods watcher filters directives computed created …(创建组件,创建vue实例)
组件-全局组件与局部组件的区别
全局变量与局部变量
<!DOCTYPE html>
<html lang="zh">
<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.11/vue.js"></script>
<style>
body{
background-color: #eee;
}
#app{
background-color:#fff;
width: 500px;
margin: 50px auto;
box-shadow: 3px 3px 3px rgba(0 , 0, 0, 0.5);padding:2em;
}
.box{
padding: 1em;
border:1px solid #ccc;
margin:1em;
}
</style>
</head>
<body>
<div id="app">
<h2>学习全局组件和局部组件</h2>
<my-com1></my-com1>
<br>
<my-com2></my-com2>
</div>
<div id="app2">
另一个vue实例
<my-com1></my-com1>
<hr>
<my-com100></my-com100>
<!-- <my-com2></my-com2> -->
</div>
<script>
// 全局组件与局部组件的区别
// 1. 定义的格式不同。
// Vue.component() : 全局组件
// components: {"": {} } : 局部组件
// 2. 有效范围不同
// 全局组件: 在随后的所有的vue实例中都可以使用,在另一个全局组件中也可以使用
// 局部组件:在哪里定义的,就只在哪里使用
Vue.component('MyCom1', {
template: `
<div>
全局-com1
</div>
`
})
Vue.component('MyCom100', {
template: `
<div>
全局-com100
<my-com1></my-com1>
</div>
`
})
var vm = new Vue({
el: '#app',
components: {
// 组件名: {
// // 配置对象
// }
"MyCom2": {
template: `
<div>
局部组件-com2
</div>
`
}
}
})
var vm2 = new Vue({
el: "#app2"
})
</script>
</body>
</html>
组件-组件之间的关系
-
父子
-
兄弟
-
祖孙
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>html页面</title>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.11/vue.js"></script>
<style>
body{
background-color: #eee;
}
#app{
background-color:#fff;
width: 500px;
margin: 50px auto;
box-shadow: 3px 3px 3px rgba(0 , 0, 0, 0.5);padding:2em;
}
.box{
padding: 1em;
border:1px solid #ccc;
margin:1em;
}
</style>
</head>
<body>
<div id="app">
<h2>html页面</h2>
<com-a>
</com-a>
</div>
<script>
// 定义嵌套关系是在 组件的模板中定义的。
// 如果在A组件的模板中使用了C组件,则称C是A的子组件;A就是C的父组件
Vue.component('ComA', {
template: `
<div>
<h1>ComA</h1>
<com-b></com-b>
<com-c></com-c>
</div>
`
})
Vue.component('ComB', {
template: `<h2>ComB</h2>`
})
Vue.component('ComC', {
template: `
<div>
<h2>ComC</h2>
<com-d></com-d>
<com-e></com-e>
</div>`
})
Vue.component('ComD', {
template: `<h3>ComD</h3>`
})
Vue.component('ComE', {
template: `<h3>ComE</h3>`
})
var vm = new Vue({
el: '#app',
data: {
}
})
</script>
</body>
</html>
组件-传值-父传子
父子: 在A组件中使用了B组件,则称A是父组件,B是子组件
正常情况下,组件之间是相互绝缘的:子组件无法直接访问父组件中的数据
目标
把从数据从父组件传给子组件
基本步骤
共两步
- 在父组件中使用子组件时,给子组件传入自定义属性;
- 子组件内部声明props来接收属性值。
原则:你情我愿(爸爸愿意传,儿子愿意接)
示例
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>html页面</title>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.11/vue.js"></script>
<style>
body{
background-color: #eee;
}
#app{
background-color:#fff;
width: 500px;
margin: 50px auto;
box-shadow: 3px 3px 3px rgba(0 , 0, 0, 0.5);padding:2em;
}
.box{
padding: 1em;
border:1px solid #ccc;
margin:1em;
}
</style>
</head>
<body>
<div id="app">
<h2>html页面</h2>
<com-a></com-a>
</div>
<script>
Vue.component('ComA', {
template: `
<div>
父组件A-父组件的数据:{
{abc}}
<com-b money="1000k" car="oooo"></com-b>
</div>
`,
data () {
return {
abc: 100 }
}
})
Vue.component('ComB', {
// props 是一个配置项,它的值是一个数组,
// 用来登记希望从父组件中接收什么数据
props: ['car', 'money'],
template: `
<div>
子组件B - 我老爸给我的钱: , 给我的车 {
{car}}
</div>
`
})
var vm = new Vue({
el: '#app',
data: {
}
})
</script>
</body>
</html>
父传子: 如果父组件中修改了数据,则子组件中接收到的也会同步修改。
验收效果
组件-传值-父传子-动态值
v-bind动态地传递值给子组件。
思考
问题1: 加:与不加:的区别
<com1 p1="a"></com1>
// 传递的值就是字符串"a"
<com1 :p1="a"></com1>
// 传递的是一个动态的值:是数据项中决定的a
问题2:有接收与无接收的区别
<com1 a="1" b="2" c="3"></com1>
如果子组件内部只有props:[“a"] ,则会如何?
在子组件中就无法收到b,c的值
并且, b,c并显示在dom元素的属性,在查看元素时,可见:
组件-案例-折叠面板-基本实现
<div id="app">
<h2>案例:折叠面板</h2>
<pannel></pannel>
<pannel></pannel>
</div>
</div>
<script src="./vue.js"></script>
<script>
var vm = new Vue({
el: '#app',
components: {
pannel: {
data(){
return {
isOpen: false}
},
template: `<div class="box">
<div class="title" >
<h4>我是标题</h4><span class="btn" @click="isOpen=!isOpen">{
{isOpen ? '收起' : '展开'}} </span>
</div>
<div class="container" v-show="isOpen">
寒雨连江夜入吴,
<br>
平明送客楚山孤。
<br>
洛阳亲友如相问,
<br>
一片冰心在玉壶。
</div>
</div>`
}
}
})
</script>
组件-案例-折叠面板-父传子
目标:对于多首诗,生成多个折叠面板
数据准备
list: [
{
id: 1,
title: '洛阳陌',
author: '李白',
content: '白玉谁家郎,回车渡天津。看花东陌上,惊动洛阳人。'
},
{
id: 2,
title: '静夜思',
author: '李白',
content: '床前明月光,疑是地上霜,举头望明月,低头思故乡。'
},
{
id: 3,
title: '赠汪伦',
author: '李白',
content: '李白乘舟将欲行,忽闻岸上踏歌声。花潭水深千尺,不及汪伦送我情。'
}
]
参考代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- <script src="./vue.js"></script> -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<style>
body{
background-color: #ccc;
}
#app{
width: 400px;
margin: 20px auto;
background-color: #fff;
border:4px solid blueviolet;
border-radius: 1em;
box-shadow: 3px 3px 3px rgba(0, 0, 0, .5);
padding:1em 2em 2em;
}
h3{
text-align: center;
}
.title{
display: flex;
justify-content: space-between;
align-items: center;
border:1px solid #ccc;
padding: 0 1em;
}
.title h4{
line-height: 2;margin:0;}
.container{
border:1px solid #ccc;
padding: 0 1em;
}
.btn {
/* 鼠标改成手的形状 */
cursor: pointer;
}
</style>
</head>
<body>
<div id="app">
<h3>案例:折叠面板</h3>
<!--
<my-pannel :title="list[0].title" :content="list[0].content"></my-pannel>
<my-pannel :title="list[1].title" :content="list[1].content"></my-pannel>
<my-pannel :title="list[2].title" :content="list[2].content"></my-pannel> -->
<my-pannel
v-for="item in list"
:title="item.title"
:content="item.content"></my-pannel>
</div>
<script>
// 封装一个组件
Vue.component('MyPannel', {
props: ['title', 'content'],
template: `
<div style="margin-bottom:20px;">
<div class="title">
<h4>{
{title}}</h4>
<span class="btn"
v-on:click="isVisiable= !isVisiable">
{
{isVisiable ? '收起' : '展开'}}
</span>
</div>
<div class="container" v-show="isVisiable">
{
{content}}
</div>
</div>
`,
data () {
return {
isVisiable: true}
}
})
const vm = new Vue({
el: "#app",
data: {
list: [
{
id: 1,
title: '洛阳陌',
author: '李白',
content: '白玉谁家郎,回车渡天津。看花东陌上,惊动洛阳人。'
},
{
id: 2,
title: '静夜思',
author: '李白',
content: '床前明月光,疑是地上霜,举头望明月,低头思故乡。'
},
{
id: 3,
title: '赠汪伦',
author: '李白',
content: '李白乘舟将欲行,忽闻岸上踏歌声。桃花潭水深千尺,不及汪伦送我情。'
}
]
}
})
</script>
</body>
</html>
要点:
- 在组件上使用v-for指令
组件-插槽-父子组件传结构
作用
允许父组件向子组件中传入自定义的结构
在父组件中使用子组件时,我们可以传入:
- 数据
- 结构
让每个折叠面板的内容区域都不一样(不是数据不同,而是整体结构都不同),则可以使用插槽技术。
定义格式
在子组件的模板中通过slot预留出来区域,充当占位符的功能。
template: `
<div>
其它元素....
<slot></slot>
其它元素....
<slot name="插槽1"></slot>
其它元素...
<slot name="插槽2"></slot>
</div>
`
注意:
- slot理解为vue约定的特殊的标签
- 如果slot有name属性,则它是叫具名插槽,否则叫默认插槽。
- 技巧: 如果只有一个插槽,则不需要写插槽名。
使用格式
在父组件中使用子组件时,传入自定义的内容(结构)即可。
## 如果子组件上只有一个默认插槽。
<子组件><div>任意内容....</div></子组件>
## 如果子组件上定义了多个插槽,则要对应标记出slot的名字。
<子组件>
<div>任意内容.将会填充默认插槽的位置...</div>
<div slot='插槽名1'>任意内容.将会填充插槽1的位置...</div>
<div slot='插槽名2'>任意内容.将会填充插槽2的位置...</div>
</子组件>
## 用template来包裹内容
<子组件><template>任意内容....</template></子组件>
示例插槽-默认插槽
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- <script src="./vue.js"></script> -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<style>
body{
background-color: #ccc;
}
#app{
width: 400px;
margin: 20px auto;
background-color: #fff;
border:4px solid blueviolet;
border-radius: 1em;
box-shadow: 3px 3px 3px rgba(0, 0, 0, .5);
padding:1em 2em 2em;
}
h3{
text-align: center;
}
.title{
display: flex;
justify-content: space-between;
align-items: center;
border:1px solid #ccc;
padding: 0 1em;
}
.title h4{
line-height: 2;margin:0;}
.container{
border:1px solid #ccc;
padding: 0 1em;
}
.btn {
/* 鼠标改成手的形状 */
cursor: pointer;
}
</style>
</head>
<body>
<div id="app">
<h1>xxxxx</h1>
<my-pannel>123</my-pannel>
<!-- <组件 a="1" b="2"> <div>xx</div> </组件> -->
</div>
<script>
// 封装一个组件
Vue.component('MyPannel', {
template: `
<div>
MyPannel
<slot></slot>
</div>
`
})
const vm = new Vue({
el: "#app"
})
</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>Document</title>
<!-- <script src="./vue.js"></script> -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<style>
body{
background-color: #ccc;
}
#app{
width: 400px;
margin: 20px auto;
background-color: #fff;
border:4px solid blueviolet;
border-radius: 1em;
box-shadow: 3px 3px 3px rgba(0, 0, 0, .5);
padding:1em 2em 2em;
}
h3{
text-align: center;
}
.title{
display: flex;
justify-content: space-between;
align-items: center;
border:1px solid #ccc;
padding: 0 1em;
}
.title h4{
line-height: 2;margin:0;}
.container{
border:1px solid #ccc;
padding: 0 1em;
}
.btn {
/* 鼠标改成手的形状 */
cursor: pointer;
}
</style>
</head>
<body>
<div id="app">
<h1>xxxxx</h1>
<my-pannel>
<!-- 向一个名为head的插槽中插入内容 -->
<div slot="head">123</div>
<h1 slot="foot">h1</h1>
</my-pannel>
<!-- <组件 a="1" b="2"> <div>xx</div> </组件> -->
</div>
<script>
// 封装一个组件
Vue.component('MyPannel', {
template: `
<div>
MyPannel
定义一个具名插槽
<slot name="head"></slot>
<hr/>
<slot name="foot"></slot>
</div>
`
})
const vm = new Vue({
el: "#app"
})
</script>
</body>
</html>
原理图
案例:用插槽来改进折叠面板
允许用户自行定义折叠面板中content区域的内容。
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>html页面</title>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.11/vue.js"></script>
<style>
body{
background-color: #eee;
}
#app{
background-color:#fff;
width: 500px;
margin: 50px auto;
box-shadow: 3px 3px 3px rgba(0 , 0, 0, 0.5);padding:2em;
}
.box{
padding: 1em;
border:1px solid #ccc;
margin:1em;
}
</style>
</head>
<body>
<div id="app">
<h2>html页面</h2>
<my-com1 :tit="title1">
<ul>
<li>床前明月光</li>
<li>疑是地上霜</li>
<li>举头望明月</li>
<li>低头思故乡</li>
</ul>
</my-com1>
<hr>
<my-com1 tit="标题2">
<p>
李白乘舟将欲行,忽闻岸上踏歌声。花潭水深千尺,不及汪伦送我情
<img src="https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=2861734160,1875195791&fm=26&gp=0.jpg" />
</p>
</my-com1>
</div>
<script>
Vue.component('MyCom1', {
props: ['tit'],
data () {
return {
isShow: true
}
},
template: `
<div>
<div class="title" style="display:flex; justify-content:space-between">
<label>{
{tit}}</label>
<button @click="isShow=!isShow">{
{isShow ? '折叠' : '展开'}}</button>
</div>
<div class="content" v-show="isShow">
<slot> 插槽 xxx - 备胎</slot>
</div>
</div>
`
})
var vm = new Vue({
el: '#app',
data: {
title1: '静夜思'
}
})
</script>
</body>
</html>
组件-插槽-默认内容
不传入就用自己的,传了,就用传进来的。
定义:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>html页面</title>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.11/vue.js"></script>
<style>
body{
background-color: #eee;
}
#app{
background-color:#fff;
width: 500px;
margin: 50px auto;
box-shadow: 3px 3px 3px rgba(0 , 0, 0, 0.5);padding:2em;
}
.box{
padding: 1em;
border:1px solid #ccc;
margin:1em;
}
</style>
</head>
<body>
<div id="app">
<h2>html页面</h2>
<!-- 不传入内容,就会显示插槽的默认内容 -->
<my-com></my-com>
<!-- 传入head插槽,会显示内容 -->
<my-com>
<div slot="head">自定义的头</div>
</my-com>
</div>
<script>
Vue.component('MyCom', {
template: `
<div style="border:1px solid #ccc; padding: 1em;margin:1em;">
<slot name="head">
我是头部,我的名字是head
</slot>
<slot>
这里是插槽的默认内容,如果你不传入,就会显示出来
</slot>
<slot name="foot">
我是底部,我的名字是foot
</slot>
</div>
`
})
var vm = new Vue({
el: '#app',
data: {
}
})
</script>
</body>
</html>
组件-插槽-只传入内容不传入最外层的容器—template
template:只是逻辑意义上的容器,不会产生真实的dom
在封装公用组件时,可能为了减少不必要的dom结构,就可以使用template来代替div
组件-传递-子传父-消息
原理
- 自定义事件 + $emit
步骤
-
在父组件中:使用子组件时,在子组件上添加自定义事件监听
<子组件 @自定义事件名1="处理事件的函数1" @自定义事件名2="处理事件的函数2"></子组件>
处理事件的函数1
: 应该定义在父组件中的methods中。 -
在子组件中:在某个时刻(根据你程序的要求),通过$emit向父组件发出事件
this.$emit(自定义事件名1) ## 子组件抛出事件之后,在父组件中,收到了这个事件,并去调用对应的回调函数
建议事件名采用烤串格式,例如:book-send。
示例代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- <script src="./vue.js"></script> -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<style>
body{
background-color: #ccc;
}
#app{
width: 400px;
margin: 20px auto;
background-color: #fff;
border:4px solid blueviolet;
border-radius: 1em;
box-shadow: 3px 3px 3px rgba(0, 0, 0, .5);
padding:1em 2em 2em;
}
h3{
text-align: center;
}
.title{
display: flex;
justify-content: space-between;
align-items: center;
border:1px solid #ccc;
padding: 0 1em;
}
.title h4{
line-height: 2;margin:0;}
.container{
border:1px solid #ccc;
padding: 0 1em;
}
.btn {
/* 鼠标改成手的形状 */
cursor: pointer;
}
</style>
</head>
<body>
<div id="app">
<h3>子传父:子组件向父组件传递消息 ,传递数据</h3>
<!--
步骤
1. 在父组件中,使用子组时,给它添加自定义事件的监听及回调
@是事件监听。v-on的简写
自定义事件名1: 一般建议用烤串写法写名字
<父组件>
<子组件
@自定义事件名1="处理事件的回调函数,是父组件的方法"
@自定义事件名2="处理事件的函数2">
</子组件>
</父组件>
2. 在子组件中,某个时刻(你自己定义的)主动抛出事件
this.$emit(自定义事件名1)
这里的this是指子组件。$emit 是系统提供的方法
结果是:当子组件中发生某事时,会调用父组件中的方法
-->
<lao-wang></lao-wang>
</div>
<script>
// 封装一个组件
Vue.component('XiaoWang', {
template: `
<div>
<button @click="hClick">打一下小王</button>
</div>
`,
methods: {
hClick () {
// 在子组件内部抛出事件,则会被父组件监听到,进而调用回调函数h1
// this.$emit
// this就是当前的组件,$emit是这个对象上的一个方法,专用来抛出事件
setTimeout(() => {
this.$emit('abc')
}, 3000)
}
}
})
Vue.component('LaoWang', {
template: `
<div>
当子组件中发生abc这个事件时,就会执行h1
<xiao-wang
@abc="h1"
></xiao-wang>
</div>
`,
methods: {
h1 (){
alert('是谁打小凡了,出来!!!')
}
}
})
const vm = new Vue({
el: "#app"
})
</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>Document</title>
<!-- <script src="./vue.js"></script> -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<style>
body{
background-color: #ccc;
}
#app{
width: 400px;
margin: 20px auto;
background-color: #fff;
border:4px solid blueviolet;
border-radius: 1em;
box-shadow: 3px 3px 3px rgba(0, 0, 0, .5);
padding:1em 2em 2em;
}
h3{
text-align: center;
}
.title{
display: flex;
justify-content: space-between;
align-items: center;
border:1px solid #ccc;
padding: 0 1em;
}
.title h4{
line-height: 2;margin:0;}
.container{
border:1px solid #ccc;
padding: 0 1em;
}
.btn {
/* 鼠标改成手的形状 */
cursor: pointer;
}
</style>
</head>
<body>
<div id="app">
<h3>子传父:子组件向父组件传递数据</h3>
<!--
步骤
1. 在父组件中,使用子组时,给它添加自定义事件的监听及回调
@是事件监听。v-on的简写
自定义事件名1: 一般建议用烤串写法写名字
<父组件>
<子组件
@自定义事件名1="处理事件的回调函数,是父组件的方法"
@自定义事件名2="处理事件的函数2">
</子组件>
</父组件>
2. 在子组件中,某个时刻(你自己定义的)主动抛出事件
this.$emit(自定义事件名1, 你要传出去的数据)
这里的this是指子组件。$emit 是系统提供的方法
结果是:当子组件中发生某事时,会调用父组件中的方法,并自动接收参数
-->
<lao-wang></lao-wang>
</div>
<script>
// 封装一个组件
Vue.component('XiaoWang', {
template: `
<div>
<button @click="hClick">打一下小王</button>
</div>
`,
methods: {
hClick () {
// 在子组件内部抛出事件,则会被父组件监听到,进而调用回调函数h1
// this.$emit
// this就是当前的组件,$emit是这个对象上的一个方法,专用来抛出事件
setTimeout(() => {
// this.$emit('事件名', 附加的数据)
// { name: '小花', action: '打了我' } 这个数据是子组件的
this.$emit('abc', {
name: '小花', action: '打了我' })
}, 3000)
}
}
})
Vue.component('LaoWang', {
template: `
<div>
当子组件中发生abc这个事件时,就会执行h1, 同时h1会自动接收参数
<xiao-wang
@abc="h1"
></xiao-wang>
</div>
`,
methods: {
h1 (obj){
// obj会自动接收来自子组件的数据
console.log('是谁打小米了,出来!!!', obj)
}
}
})
const vm = new Vue({
el: "#app"
})
</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>Document</title>
<!-- <script src="./vue.js"></script> -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<style>
body{
background-color: #ccc;
}
#app{
width: 400px;
margin: 20px auto;
background-color: #fff;
border:4px solid blueviolet;
border-radius: 1em;
box-shadow: 3px 3px 3px rgba(0, 0, 0, .5);
padding:1em 2em 2em;
}
h3{
text-align: center;
}
.title{
display: flex;
justify-content: space-between;
align-items: center;
border:1px solid #ccc;
padding: 0 1em;
}
.title h4{
line-height: 2;margin:0;}
.container{
border:1px solid #ccc;
padding: 0 1em;
}
.btn {
/* 鼠标改成手的形状 */
cursor: pointer;
}
</style>
</head>
<body>
<div id="app">
<h3>案例:折叠面板</h3>
<open
v-for="(item,idx) in list"
:curidx="curidx"
:idx=idx
:title="item.title"
:content="item.content"
@do-open="hOpen"
></open>
</div>
<!--
1. 定义一个组件,用来接收标题及内容,并显示出来
2. v-for + 父传子 把内容,标题,下标 都传进去
3. 在父组件中添加一个数据项用来记录当前唯一处于打开的状态的面板的下标
4. 子传父。 在子组件中点击时,要把当前的下标传给父组件,并保存
5. 父传子。 把 要打开的面板下标 传给子组件,在子组件的内部,对比:
- 自己的下标 和 当前下村 是否相同,如果相同,就展示内容。
- 三元,决定 文字
-->
<script>
Vue.component('open', {
// idx:当前面板的下标
// curidx: 此时,要处于打开状态的面板的下标
props: ['title', 'content', 'idx', 'curidx'],
template: `
<div style="border:1px solid #ccc; margin: 1em; padding:1em;">
<div>
<span style="float:right; cursor:pointer" @click="h1">
{
{ idx === curidx ? '关闭' : '打开'}}
</span>
<h5>
{
{idx}}-{
{title}}
</h5>
</div>
<div v-show="idx === curidx">
{
{content}}
</div>
</div>
`,
methods: {
h1 () {
// 在组件内,访问父组件传入的porp时,在代码中,要加this.
// 如果当前是打开状态
if (this.curidx == this.idx) {
// 由于面板中没有一个下标是-1,所以就全部关闭了
this.$emit('do-open', -1)
} else {
// 要打开下标
this.$emit('do-open', this.idx)
}
}
}
})
const vm = new Vue({
el: "#app",
data: {
curidx: 100, // 当前处于打开状态的 面板 的下标
list: [
{
id: 1,
title: '洛阳陌',
author: '李白',
content: '白玉谁家郎,回车渡天津。看花东陌上,惊动洛阳人。'
},
{
id: 2,
title: '静夜思',
author: '李白',
content: '床前明月光,疑是地上霜,举头望明月,低头思故乡。'
},
{
id: 3,
title: '赠汪伦',
author: '李白',
content: '李白乘舟将欲行,忽闻岸上踏歌声。桃花潭水深千尺,不及汪伦送我情。'
}
]
},
methods: {
hOpen (index) {
console.log('当前要打开的面板下标是', index)
this.curidx = index
}
}
})
</script>
</body>
</html>
在组件上使用v-model
复习: 前面使用v-model时,只是表单元素上使用。
<input v-model="xxxx" />
这节课,讲如何在组件使用v-model
<com1 v-model="xxx" />
v-model的作用:双向绑定。
在组件上使用的,也是一样希望达到双向绑定的效果:
- 父组件的数据改变时,能传给子组件;
- 子组件中的数据变化时,也能回传给父组件
v-model的原理
它是一个语法糖(是一种很复杂的写法的简写)
在子组件使用了v-model="num",它自动做两件事:
1. 给子组件添加了一个名为value的属性。相当于传一个名为value
属性给子组件。
类似于:
<my-com :value="num"></my-com>
2. 会自动给子组件上一个名为input的事件监听,
在这个事件发生时,将回传的数据直接保存到num中。
<my-com @input="(obj)=>{ this.num = obj }"></my-com>
操作
如何实现父传子(父组件中修改了数据,子组件能收到)?
- 有一个名为value的属性绑定。
如何实现子传父 (在子组件修改了数据,也能同步到父组件)?
- 在子组件内部手动调用this.$emit(‘input’, 新值)
示例代码
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>在组件上使用v-model</title>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.11/vue.js"></script>
<style>
body{
background-color: #eee;
}
#app{
background-color:#fff;
width: 500px;
margin: 50px auto;
box-shadow: 3px 3px 3px rgba(0 , 0, 0, 0.5);padding:2em;
}
.box{
padding: 1em;
border:1px solid #ccc;
margin:1em;
}
</style>
</head>
<body>
<div id="app">
<h2>在组件上使用v-model</h2>
<!--
在子组件使用了v-model="num",它自动做两件事:
1. 给子组件添加了一个名为value的属性。相当于传一个名为value
属性给子组件。
类似于:
<my-com :value="num"></my-com>
2. 会自动给子组件上一个名为input的事件监听,
在这个事件发生时,将回传的数据直接保存到num中。
<my-com @input="(obj)=>{ this.num = obj }"></my-com>
-->
<!-- <my-com :value="num" @input="h1"></my-com> -->
<hr>
<my-com v-model="num"></my-com>
</div>
<script>
Vue.component('MyCom', {
props: ['value'] ,
template: `
<div>
子组件- 从v-model中获取的数据:{
{value}}
</div>
`,
created () {
console.log('MyCom')
setTimeout( () => {
this.$emit('input', 10001)
}, 3000)
}
})
var vm = new Vue({
el: '#app',
data: {
num: 101
}
// methods: {
// h1 (obj) {
// console.log('obj', obj)
// this.num = obj
// }
// }
})
</script>
</body>
</html>
应用场景
v-model是一个语法糖,可以用来简化代码(有这种父子双向数据传递时)。
一般在ui组件库使用非常多。
如有不足,请多指教,
未完待续,持续更新!
大家一起进步!