问题导入
下面的代码是一个折叠面板的效果。
<!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>我是标题12</h4><span class="btn" @click="isOpen=!isOpen">{{isOpen ? '收起' : '展开'}} </span>
</div>
<div class="container" v-show="isOpen">
我是内容
<br>
我是内容
</div>
</div>`
}
}
})
</script>
vue-devtools
在使用组件的时候,不好观察效果,vue官方提供了一个针对vue项目的调试工具。
使用效果
安装
方法一:chrome浏览器应用商店下载
- https://chrome.google.com/webstore/category/extensions?hl=zh-CN 安装。
方法二:本地安装步骤:
1、解压今天资料中的 vue-devtools.zip 文件,注意:解压到某个不会改动的目录。
2、进入chrome浏览的扩展程序
3、打开开发者模式
4、加载已解压的扩展程序
用vue-devtools 来观察上的组件
通过浏览器打开使用了vue技术的网页
学习组件的使用
理解组件
vue组件是可复用的 Vue 实例,它可以包含独立的HTML结构,CSS样式,JS代码。它可以把一个完整的页面,根据界面功能拆分成若干组件。
js模块,独立的一个js文件,提供js逻辑,函数。
组件底层是什么?
- 组件本质上是一个vue实例,但是写法和vue实例不同,
new Vue({//配置对象})
- 在Vue实例中
new Vue({components:{//组件}})
- 组件有配置对象:
- 不包含 el 选项,el是指定vue实例(或者称为根组件)管理的视图容器。
- 通过 template选项,指定html结构容器,每个组件模板有且只有一个根元素。
- data数据项必须是一个函数,通过它的返回值来设置数据。
- 配置选项:data methods watcher filters directives computed created …(创建组件,创建vue实例)
定义格式
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>
基本示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<h3>学习组件的使用</h3>
<city-qj></city-qj>
<br>
<comp1></comp1>
<comp1></comp1>
<br>
</div>
<script src="./vue.js"></script>
<script>
// 1. 组件是什么
// 组件是可复用的Vue实例。
// 2. 如何定义
// new Vue ({
// el:,//
// components: {
// // 定义组件
// // 组件名1 : 对组件的配置项,它是一个对象.
// // 在new Vue({ }) 可以写的配置项,在组件中基本上都可以用
// comp1: {
// // 组件的data与vue实例有一点区别:它必须是一个函数,通过这个函数来返回对象,
// // 在对象中定义自己的数据项
// data: function() {
// return {
// // 组件自己的数据项
// }
// },
// // 用来约定组件的模板。它等价于vue实例的 el
// // 它只能一个根元素
// // template: `<div></div><h1></h1>` // 不ok
// template: `<div></div>` // ok
// watch:,
// computed:,
// filters,
// directives,
// components,
// }
// }
// })
// 3. 如何使用
// 像使用dom标签一样使用,标签就是组件名
// 要点:
// 1. 数据。
// 组件是自成体系,它的数据只能自己用,在其它组件不能使用;它不能直接使用vue实例中的数据
// 格式:data(){ return {} }
// 2. 视图。由template决定。
// 3. methods
// 与vue实例中的一样,定义,去调用。
// 它只能调用自己的定义的methods
const vm = new Vue({
el: '#app',
data: {
name: 'js'
},
methods: {
f1 () {
}
},
components: {
"city-qj": {
// 定义数据:只能在当前组件中使用
data() {
return {
teC: '小龙虾'
}
},
// 设置视图
template: `<div>
<h4>城市:潜江</h4>
<p>{{teC}}</p>
<button @click="hClick">点我</button>
</div>`,
// 定义方法:只能在当前组件中使用
methods: {
hClick () {
// 它的this是组件自己
console.log(this.teC)
}
}
},
comp1: {
template: '<div><h1>comp1组件</h1></div>'
}
}
})
</script>
</body>
</html>
组件名和父子组件
<div id="app">
<MyComp1></MyComp1>
<myComp1></myComp1>
<my-comp1></my-comp1>
</div>
<script src="./vue.js"></script>
<script>
new Vue({
el: '#app',
components: {
MyComp1:{template:`<div>子组件:MyComp1 大驼峰</div>`},
myComp1:{template:`<div>子组件:myComp1 小驼峰</div>`},
mycomp1:{template:`<div>子组件:mycomp1 纯小写</div>`}
}
});
</script>
-
父子组件
- 如果组件A在组件B中被调用,则称组件B是组件A的父组件,而组件A是组件B的子组件
-
组件名:
- 在定义组件时,组件名可以是小驼峰(myCom1),大驼峰的写法(MyCom1)
- 在模板中使用组件时,全采用全小写的烤串写法(my-com1)
组件的嵌套
一个组件内部,还可以继续定义,使用组件。
<div id="app">
<my-comp1></my-comp1>
</div>
<script src="./vue.js"></script>
<script>
new Vue({
el: '#app',
components: {
myComp1:{
template:`<div>子组件 <abcd></abcd></div>`,
components:{
abcd:{template:`<h5>ABCD</h5>`}
}
}
}
});
</script>
小结
vue中的组件系统让我们可以像搭积木一样,通过不同的组件来建立整个页面。
父子组件传值-从数据从父组件传给子组件
基本步骤
-
在父组件中使用子组件时,给子组件传入自定义属性;
-
子组件内部声明props来接收属性。
原则:你情我愿
示例
<div>
<com1 abc="vue" :myMsg="info"></com1>
</div>
components:{
com1:{
template: `<div>子组件:{{myMsg}}</div>`,
// props 用来接收使用组件所绑定的属性数据,属性的名字就是数据的字段名称
// props: ['abc'] 接收到了使用组件绑定的abc属性属性,使用vue实例即可访问,this.abc
// 此时的 this.myMsg 其实就是父组件传递给子组件的数据
props: ['abc','myMsg']
})
- 只读不能修改
- 父组件中修改了值,也会影响子组件中的Props值
案例-折叠面板
<style>
body{
background-color: #eee;
}
#app{
background-color:#fff;
width: 500px;
margin: 50px auto;
text-align: center;
box-shadow: 3px 3px 3px rgba(0 , 0, 0, 0.5);
padding:1em 2em;
}
.title {
line-height: 1;
display: flex;
justify-content: space-between;
align-items: center;
}
.btn{ cursor: pointer;}
.container {
border: 1px solid #ccc;
padding: 1em;
}
</style>
</head>
<body>
<div id="app">
<h2>案例:折叠面板</h2>
<pannel title="标题1" content="内容1"></pannel>
<pannel title="标题2" content="内容2"></pannel>
</div>
</div>
<script src="./vue.js"></script>
<script>
const pannel = {
props: ['title','content'],
data(){
return {isOpen: false}
},
template: `<div class="box">
<div class="title" >
<h4>{{title}}</h4><span class="btn" @click="isOpen=!isOpen">{{isOpen ? '收起' : '展开'}} </span>
</div>
<div class="container" v-show="isOpen">
{{content}}
</div>
</div>`
}
var vm = new Vue({
el: '#app',
components: {
pannel: pannel
}
})
</script>
组件中的插槽
对于前面的折叠面板组件,如果使用组件时,希望传入一些比较复杂的内容,让每个折叠面板的内容区域都不一样(不仅是数据不同,而是整体结构都不同),则可以使用插槽技术。
定义格式
在子组件的模板中通过slot预留出来区域,充当占位符的功能。
template: `
其它元素....
<slot></slot>
其它元素....
<slot name="插槽1"></slot>
其它元素...
<slot name="插槽2"></slot>
`
如果slot有name属性,则它是叫具名插槽,否则叫默认插槽
技巧: 如果只有一个插槽,则不需要写插槽名。
使用格式
在使用子组件时,传入自定义的内容即可。
单个插槽
如果子组件上只有一个默认插槽。
<子组件><div>任意内容....</div></子组件>
多个插槽
如果子组件上定义了多个插槽,则要对应标记出slot的名字。
<子组件>
<div>任意内容.将会填充默认插槽的位置...</div>
<div slot='插槽名1'>任意内容.将会填充插槽1的位置...</div>
<div slot='插槽名2'>任意内容.将会填充插槽2的位置...</div>
</子组件>
默认插槽使用示例
<div id="app">
<com1></com1>
<com1>
<h1>我是父组件中来填充默认插槽的内容</h1>
</com1>
</div>
<script>
var vm = new Vue({
el: '#app',
components: {
com1: {
template: `<div>我是子组件com1,<slot>这里默认插件的默认内容</slot> </div>`
}
}
})
</script>
具名插槽使用示例
<div id="app">
<com1></com1>
<com1>
<h1 slot="slot1">我是父组件中来填充具名插槽的内容</h1>
</com1>
</div>
<script>
var vm = new Vue({
el: '#app',
components: {
com1: {
template:
`<div>我是子组件com1,
<slot>这里默认插件的默认内容</slot>
<slot name="slot1"></slot>
</div>`
}
}
})
</script>
案例:用插槽来改进折叠面板
允许用户自行定义折叠面板中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 tit="标题1">
<table>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
</table>
</my-pannel>
<my-pannel tit="标题2">
<img src="http://img3m9.ddimg.cn/32/4/28530149-1_w_7.jpg">
<!-- 图片 -->
</my-pannel>
<my-pannel tit="标题3">
<div>
寒雨连江夜入吴,
<br>
平明送客楚山孤。
<br>
洛阳亲友如相问,
<br>
一片冰心在玉壶。
<br>
</div>
</my-pannel>
</div>
<script>
// 对于折叠面板:
// 特点:
// - 标题一般会比较简单,所以使用props传值
// - 内容可能比较复杂,通过插槽技术允许用户传入任意内容
// 由于只需要一个插槽,所以就直接使用默认插槽就行。
// 下面是用组件来解决的
const vm = new Vue({
el: "#app",
components: {
// 定义的组件的名字
MyPannel: {
data () {
return {
isVisiable: true, // 表示当前 内容是否显示
}
},
props:['tit'],
template: `
<div>
<div class="title">
<h4>{{tit}}</h4>
<span class="btn"
v-on:click="isVisiable= !isVisiable">
{{isVisiable ? '收起' : '展开'}}
</span>
</div>
<div class="container" v-show="isVisiable">
<slot></slot>
</div>
</div>
`
}
}
})
</script>
</body>
</html>
子组件传值给父组件
背景:
在子组件中需要向父组件传值
原理
- 自定义事件 + emit
步骤
-
在父组件中:使用子组件时,在子组件上添加自定义事件监听
<子组件 @自定义事件名1="处理事件的函数1" @自定义事件名2="处理事件的函数2"></子组件>
-
在子组件中:在某个时刻,通过$emit向父组件传递事件,并附加参数
this.$emit(自定义事件名1,附加参数)
示例
<div id="app">
<comp1 @gameover1="hGameover1" @over="hover"></comp1>
</div>
methods: {
hover (param) {
console.log(param)
}
},
components:{
comp1:
{
template:`<div>
<button @click="$emit('gameover')"> 发消息 </button>
<button @click="hClick"> 发消息 </button>
</button></div>`,
methods: {
hclick () {
this.$emit('over', {a:1,b:2})
}
}
}
}
案例-测试题
参考代码:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</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;
}
.subject{
margin:5px;
padding:5px;
font-size: 20px;
}
.subject span {
display:inline-block;
text-align: center;
width: 20px;
}
.subject input {
width: 50px;
height: 20px;
}
.right{color:green}
.error{
color:red;
}
.undo{color:#ccc;}
</style>
</head>
<body>
<div id="app">
<h2>测试题</h2>
<subject v-for="idx in count" :idx="idx" @submit='hGetAns'></subject>
<div>
<flag
v-for="(item,idx) in result"
:idx="item.idx" :ans="item.ans"></flag>
</div>
</div>
<script>
var vm = new Vue({
data: {
count:5,
result:[]
},
created () {
for(var i = 1; i<= this.count;i++) {
this.result.push({idx:i,ans:'未完成'})
}
},
components: {
flag: {
props:["idx","ans"],
computed: {
cCla () {
return this.ans ==='正确' ? 'right': (this.ans==='错误'?'error':'undo')
}
},
template: `<span :class="cCla">{{idx}}: {{ans}}</span>`
},
subject: {
props:["idx"],
template: `<div class="subject">
<span>{{num1}}</span>+<span>{{num2}}</span> = <input v-model.trim.number="result" :disabled="isSubmit"/>
<button :disabled="isSubmit" @click="hSubmit">提交</button>
</div>`,
data:function() {
return {
num1: Math.ceil(Math.random()*10),
num2: Math.ceil(Math.random()*10),
result: '',
isSubmit:false
}
},
methods: {
hSubmit () {
let rs = this.result == (this.num1 + this.num2)
this.isSubmit = true
this.$emit('submit', {ans:rs,idx:this.idx})
}
}
}
},
methods: {
hGetAns (result) {
// console.log(result)
const rs = this.result.find(it => it.idx === result.idx)
rs.ans = result.ans ? '正确' : '错误'
}
}
}).$mount('#app')
</script>
</body>
</html>
全局组件
在vue实例中定义的组件称为局部组件,它只能当前vue实例中使用;
在vue实例之外的定义的组件称为全局组件,它可以在多个vue实例中使用,方便封装组件共享给他人使用。
定义格式
Vue.component('组件名称','组件配置对象')
这里的组件配置对象与上述局部组件的方法一样。
使用格式
与局部组件一致。
示例
<div id="app">
<!-- 注册组件时候使用的组件名称,在视图中使用就是自定义标签的名称 -->
<!-- 注意:组件的名称不能和原生html标签重名 -->
<com-article></com-article>
</div>
<script src="./vue.js"></script>
<script>
// 全局注册组件
Vue.component('com-article', {
// 组件配置对象,和vue实例的配置对象基本一致,没有el选项
// template选项必须指定,当前组件管理的视图,需要有一个根标签。
// 在模板字符中,可以使用插值表达式,和任意指令,但是只能使用当前组件数据和函数。
template: '<div>{{count}} <button @click="add()">自增1</button></div>',
// 声明数据,使用的还是data,但是是一个函数,函数的返回值才是组件的数据,必须是对象。
data () {
return {
count: 100
}
},
// 可以使用任意配置选项
methods: {
add () {
this.count ++
}
}
})
// 根实例
const vm = new Vue({
el: '#app'
})
</script>
件称为局部组件,它只能当前vue实例中使用;
在vue实例之外的定义的组件称为全局组件,它可以在多个vue实例中使用,方便封装组件共享给他人使用。
定义格式
Vue.component('组件名称','组件配置对象')
这里的组件配置对象与上述局部组件的方法一样。
使用格式
与局部组件一致。
示例
<div id="app">
<!-- 注册组件时候使用的组件名称,在视图中使用就是自定义标签的名称 -->
<!-- 注意:组件的名称不能和原生html标签重名 -->
<com-article></com-article>
</div>
<script src="./vue.js"></script>
<script>
// 全局注册组件
Vue.component('com-article', {
// 组件配置对象,和vue实例的配置对象基本一致,没有el选项
// template选项必须指定,当前组件管理的视图,需要有一个根标签。
// 在模板字符中,可以使用插值表达式,和任意指令,但是只能使用当前组件数据和函数。
template: '<div>{{count}} <button @click="add()">自增1</button></div>',
// 声明数据,使用的还是data,但是是一个函数,函数的返回值才是组件的数据,必须是对象。
data () {
return {
count: 100
}
},
// 可以使用任意配置选项
methods: {
add () {
this.count ++
}
}
})
// 根实例
const vm = new Vue({
el: '#app'
})
</script>