vue组件化开发
在现行的MVVM数据驱动页面的框架里面(vue/react/angular
),它们都是支持组件化开发的,组件化开发也叫虚拟dom(virtual-dom
)开发
一、存在的问题
<body>
<div id="app">
<ul class="list-group">
<li>张三</li>
<li>李四</li>
<li>王五</li>
</ul>
<ul class="list-group">
<li>张三</li>
<li>李四</li>
<li>王五</li>
</ul>
<ul class="list-group">
<li>张三</li>
<li>李四</li>
<li>王五</li>
</ul>
</div>
</body>
我们平常在做开发的时候,有时候发现页面上面有很多布局或结构都是相同的时候,我们不得不把这些代码都写一次,然后复制粘贴 ,这样代码会变得非常多
这个时候代码的冗余量会非常大。按照我们之前的思路,我们会想到封装。现在的问题就是HTML是否可以封装?
二、关于virtual DOM
在上一个章节,我们其实已经得到了一个问题,如果HTML标签要是能够 封装,那就可以解决这个问题,现在的vue框架就可以实现这样效果
在vue里面,有两个核心点
- 数据驱动页面
- 虚拟DOM(
virtual-dom
)开发,这一种开发就是组件化的开发
到底什么是virutal-dom,我们现在来尝试一下
<body>
<div id="app">
<user-list></user-list>
<hr>
<user-list></user-list>
<hr>
<user-list></user-list>
</div>
<!-- template不能在托管区域里面 -->
<template id="temp1">
<ul class="list-group">
<li>张三</li>
<li>李四</li>
<li>王五</li>
</ul>
</template>
</body>
<script src="./js/vue.js"></script>
<script>
Vue.component("user-list",{
template:"#temp1"
})
new Vue({
el:"#app",
data:{
}
})
</script>
这个时候我们通过上面的代码就已经实现之前对HTML的封装
代码说明:
- 要把所有封装的
html
放在<template>
的标签里面,并且定义一个不重复的id- 在JS里面要通过
Vue.component()
这个方法来注释一个组件,名叫user-list
的虚拟标签- 在HTML的页在上面通过使用
<user-list></user-list>
来调用这个组件这个时候我们就可以看到,在网页当中本身是没有
<user-list>
这一个标签的,但是现在又有了这个标签,所以这个标签应该算是一个虚拟的标签,这种技术就叫virtual-dom
开发,也就是组件化开发
在前端的三大框架里都支持组件化的virtual-dom
开发,但是都有一些共同的特点
注意事项
- 组名的名称(
virutal-dom
的名称)不能是html里面已经存在的标签名称 - 组个名称如果是驼峰命名的,则需要进行转义操作,如
userList
就会转义成<user-list>
template
不能写在vue的托管区域里面template
标签下面是不支持片段代码的,通俗一点就是只能有一个根标签
标哥特别提醒:vue的组件其实可以看成是一个小型的vue
三、全局组件
顾名思义全局组件就是可以在全局范围内使用的
全局组件使用Vue.component
这个方法来实现的,上一个章节的内部就是全局组件
-
先准备一个
<tempalte>
标签,然后设置一个id,把要封装的html写在里面去<template id="temp1"> </template>
当然上面的
<template>
也可以换成<script type="text/template" id="temp1">
千万千万记得,不能放在托管区域里在,也不能有片段代码
-
在js代码当调调用
Vue.component
方法,把之前定义的HTML代码变成是一个组件Vue.component("c", { template: "#temp1" });
📚 案例
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>全局组件</title>
<style>
.box{
border: 1px solid black;
margin: 10px;
}
</style>
</head>
<body>
<div id="app">
<one></one>
</div>
<template id="temp1">
<div class="box">
<h2>我是第一个组件</h2>
<two></two>
<three></three>
</div>
</template>
<template id="temp2">
<div class="box">
<h2>我是第二个组件</h2>
<three></three>
</div>
</template>
<template id="temp3">
<div class="box">
<h2>我是第三个组件</h2>
</div>
</template>
</body>
<script src="./js/vue.js"></script>
<script>
Vue.component("one",{
template:"#temp1"
});
Vue.component("two",{
template:"#temp2"
});
Vue.component("three",{
template:"#temp3"
});
new Vue({
el: "#app"
})
</script>
</html>
上面就是一个最典型的全局组件的案例,全局组件只要注册成功,就可以在任何范围内使用
四、局部组件
顾名思义就是只可以在局部范围内使用,这一点正好是与全局组件相反
在学习局部组件的时候,请同样一定记住一句话vue的组件其实可以看成是一个小型的vue
-
编写一个
<template>
的区域,这一点与全局组件相同<template id="temp1"> <div> <h2>这是一个组件</h2> </div> </template>
-
创建一个对象,指定组件的接管区域【这一点与vue很像,vue是通过
el
属性去接管,而组件是通过tempalte
属性接管】var one = { template: "#temp1" }
当上面的代码完成了以后,它还仅仅只是一个对象
-
要在哪里使用就在哪里把这个对象注册成一个组件
new Vue({ el: "#app", components:{ userList:one } })
局部组件本身是一个对象,在使用之前一定要注册
📕 案例
<body>
<div id="app">
<one></one>
</div>
<template id="temp1">
<div class="box">
<h2>第一个组件</h2>
<two></two>
<three></three>
</div>
</template>
<template id="temp2">
<div class="box">
<h2>第二个组件</h2>
<three></three>
</div>
</template>
<template id="temp3">
<div class="box">
<h2>第三个组件</h2>
</div>
</template>
</body>
<script src="./js/vue.js"></script>
<script>
var three = {
template:"#temp3"
};
var two = {
template:"#temp2",
components:{
three
}
};
var one = {
template:"#temp1",
components:{
two,three
}
};
new Vue({
el: "#app",
components:{
one
}
})
</script>
局部组件在使用之前一定要事先注册一下,而注册的方法很简单,把封装组件的对象放在components
属性下面即可
五、组件中的数据
一个组件其实就是一个小型的vue,组件的内部也是可以有数据,也是可以有方法,也是可以有事件等
vue的组件与组件之前,及vue的内部都是相互独立,相互隔离,默认不进行相互通信,通过下面的例子可以看到
1. 组件内部的事件
<body>
<div id="app">
<button type="button" @click="sayHello">外边的铵钮</button>
<hr>
<one></one>
</div>
<template id="temp1">
<div>
<button type="button" @click="sayHello">按钮</button>
</div>
</template>
</body>
<script src="./js/vue.js"></script>
<script>
var one = {
template: "#temp1",
methods:{
sayHello(){
alert("我是内部的方法")
}
}
}
new Vue({
el: "#app",
methods: {
sayHello() {
alert("我是外边的方法")
}
},
components: {
one
}
})
</script>
在上面的代码里面, 我们可以看到,组件内部的也是可以有方法的,它的方法和vue里面的方法是一样的
2. 组件自身数据
如果一个组件的内部需要数据是可以自己产生的,这一点与vue
很像,但是语法上面又有一些区别 ,它内部也有一个data
属性,但组件内部的data
是一个方法,这个方法所返回的对象才是真正的组件数据
<body>
<div id="app">
<h2>{{userName}}</h2>
<hr>
<one></one>
</div>
<template id="temp1">
<div>
<h3>组件内的姓名:{{stuName}}</h3>
<ul>
<li v-for="(item,index) in list" :key="index">{{item}}</li>
</ul>
</div>
</template>
</body>
<script src="./js/vue.js"></script>
<script>
var one = {
template: "#temp1",
data(){
return {
stuName:"陈欢欢",
list:["a","b","c","d"]
}
}
}
new Vue({
el: "#app",
data:{
userName:"张三"
},
components: {
one
}
})
</script>
3. 组件接收外部数据
<body>
<div id="app">
<list-box :aaa="['a','b','c']"></list-box>
</div>
<template id="temp1">
<div>
{{aaa}}
</div>
</template>
</body>
<script src="./js/vue.js"></script>
<script>
// 数据要么在自身,要么从外边拿
let listBox = {
template: "#temp1",
props:{
aaa:{
type:Array,
required:true
}
},
data() {
return {
}
}
}
new Vue({
el: "#app",
data: {
},
components: {
listBox
}
})
</script>
在上面的代码当中,我们可以看到,组件内部有一个数据
aaa
是从外边获取到的数据在组件的内部通过
props
来接收传递过来的值,type
表示接收的数据类型,requierd
表示这个值必须要传给我
在接收值的时候其实也是有两种语法的,一种是对象语法,一种是数组语法
对象语法
props:{
aaa:{
type:类型,
required:boolean,
default(){
return 默认值
}
},
bbb:{
type:类型....
}
}
数组语法
props:["aaa","bbb"]
注意事项
无论是对象语法还是数组语法,在使用属性的的时候,都要注意驼峰命令的情况,如果属性非要使用驼峰命名请转义, 如<c :user-list="['a','b']"></c>
,则在在接收的时候就可以使用props:["userList"]
<body>
<div id="app">
<list-box :list-info="stuList"></list-box>
<hr>
<list-box :list-info="teacherList"></list-box>
</div>
<template id="temp1">
<ul class="list-group">
<li v-for="(item,index) in listInfo" :key="index">
{{item}}
</li>
</ul>
</template>
</body>
<script src="./js/vue.js"></script>
<script>
// 数据要么在自身,要么从外边拿
let listBox = {
template: "#temp1",
props: {
listInfo:{
type:Array,
required:true
}
}
}
new Vue({
el: "#app",
data: {
stuList: ["季强", "陈欢欢", "喻金", "松明"],
teacherList: ["桃子", "标哥"]
},
components: {
listBox
}
})
</script>
六、组件的事件及方法
之前已经给同学样讲过了,每个组件都是相互隔离的,如果要相互之间使用数据,则必须要进行传递,外部的数据传递到组件内部,这样就会形成一种关系叫父子关系,A使用了B,则我们就认为A是B的父级
1. 父级组件调用子级组件的方法【第一种方式】
在组件的部是两个特殊的属性的
$parent
它指向自己的父级组件$children
它包含当前组里面所使用的所有子组件
案例
<body>
<div id="app">
<button type="button" @click="outer1">外部的按钮</button>
<one></one>
</div>
<template id="temp1">
<div class="box">
<button type="button" @click="aaa">内部的按钮</button>
</div>
</template>
</body>
<script src="./js/vue.js"></script>
<script>
let one = {
template: "#temp1",
methods: {
aaa() {
alert("我是内部的按钮的事件");
console.log("我的父级",this.$parent);
}
}
}
new Vue({
el: "#app",
methods:{
outer1(){
alert("我是外部的东西");
//想在这里调用里面的aaa怎么办?
this.$children[0].aaa();
}
},
components:{
one
}
})
</script>
在上面的案例里面,我们可以通过
$children
来找到所使用了的所有子组件
上面的代码里面是极其不严格的,因为它是通过$children[索引]
来找到子组件的,如果子组件的索引不对,则一切白费
2. 父级组件调用子级组件的方法【第二种方式】
子级组件其实也可以看成是一个dom
,只是它是一个虚拟dom,而vue支持dom操作
<body>
<div id="app">
<button type="button" @click="outer1">外部的按钮</button>
<one ref="ttt"></one>
<h2 ref="ggg">这是一个真实的DOM</h2>
</div>
<template id="temp1">
<div class="box">
<button type="button" @click="aaa">内部的按钮</button>
</div>
</template>
</body>
<script src="./js/vue.js"></script>
<script>
let one = {
template: "#temp1",
methods: {
aaa() {
alert("我是内部的按钮的事件");
}
}
}
new Vue({
el: "#app",
methods:{
outer1(){
alert("我是外部的东西");
//想在这里调用里面的aaa怎么办?
//虚拟dom来操作
this.$refs["ttt"].aaa();
}
},
components:{
one
}
})
</script>
在上面的代码里面,我们可以使用
ref
的方式来操作,
七、数据流的单向性
现有如下案例
<body>
<div id="app">
<h2>我是{{userName}},我有一个女儿叫{{daughterName}}</h2>
<hr>
<aa :daughter-name="daughterName"></aa>
</div>
<template id="temp1">
<div class="box">
<h2>我是女儿,我的名子叫:{{daughterName}}</h2>
</div>
</template>
</body>
<script src="./js/vue.js"></script>
<script>
var aa = {
template: "#temp1",
props: ["daughterName"]
}
new Vue({
el: "#app",
data: {
userName: "标哥哥",
daughterName: "杨柳彤"
},
components: {
aa
}
})
</script>
在上面的案例里面,我们向子级组件传递了一个数据:daughter-name="daughterName"
,子级组件拿到了以后就显示在了页面上面,所以数据是由父级组件传递子级组件
现有2个问题,
- 父级组件如果改变了
daughterName
这个数据,子级是否会改变? - 子级能不能改变父级传递过来的
daughterName
?
结论一:父级改变,子级也会改变,具体看下面代码
<div id="app">
<h2>我是{{userName}},我有一个女儿叫{{daughterName}}</h2>
<button type="button"
style="background-color: red;"
@click="daughterName='杨妞'">
父级在改
</button>
<hr>
<aa :daughter-name="daughterName"></aa>
</div>
在上面的代码里面,我们添加啊一个按钮,在这个按钮里绑定事件,改变当前vue里面的daughterName,结果发现子级组件也改了
结论二:子级不能更改父级传递过来的数据,一旦更改就会报错,破坏了数据的统一,具体看下面代码
<template id="temp1">
<div class="box">
<h2>我是女儿,我的名子叫:{{daughterName}}</h2>
<button type="button" @click="daughterName='爱莎公主'">子级要改变名子</button>
</div>
</template>
在组件内部的模板里面,改了外部传递过来的数据,则会报错
在上面的两个结论里面,我们可以到这就是数据流的单向性,只能由父级到子级,不能由子级到父级
八、破坏数据流单向性
正常情况下,我们是不需要破坏数据流的单向性的,但是如果某些特殊场景需要我们去改变,这个时候也可以使用下面的两种方式
1. 利用对象的堆栈原理
vue在进行组件传值的的时候使用的是浅拷贝规则 ,所以如果我们传递的是一个基本数据类型,数据在传递以后是互不影响 的,如果要让两个数据之间有相互的关系,可以使用对象来完成,如下
<body>
<div id="app">
<h2>我是{{userName}},我有一个女儿叫{{obj.daughterName}}</h2>
<button type="button"
style="background-color: red;"
@click="obj.daughterName = '杨妞'">
父级在改
</button>
<hr>
<aa :bbb="obj"></aa>
</div>
<template id="temp1">
<div class="box">
<h2>我是女儿,我的名子叫:{{bbb.daughterName}}</h2>
<button type="button"
@click="bbb.daughterName='杨爱莎公主'">
子级要改变名子
</button>
</div>
</template>
</body>
<script src="./js/vue.js"></script>
<script>
var aa = {
template: "#temp1",
props: ["bbb"]
}
new Vue({
el: "#app",
data: {
userName: "标哥哥",
obj: {
daughterName: "杨柳彤"
}
},
components: {
aa
}
})
</script>
2. 通过vue官方提供的方法--自定义事件
<body>
<div id="app">
<h1>我有一个女儿,她的名子叫{{myDaughterName}}</h1>
<one :tname="myDaughterName"
@dadchangemyname="changeDaughterName"></one>
</div>
<template id="temp1">
<div class="box">
<h2>我是女儿,我的名子是{{tname}}</h2>
<button type="button" @click="childChangeName">子级改变姓名</button>
</div>
</template>
</body>
<script src="./js/vue.js"></script>
<script>
let one = {
template: "#temp1",
props: ["tname"],
methods: {
childChangeName() {
//触发一个自定义事件
this.$emit("dadchangemyname","杨爱莎公主")
}
}
}
new Vue({
el: "#app",
data: {
myDaughterName: "杨柳彤"
},
methods: {
changeDaughterName(newName) {
this.myDaughterName = newName;
}
},
components: {
one
}
})
</script>
在组件的内部触发了一个自定义的事件,然后这个自定义的事件上面通过
this.$emit("事件名",参数)
,在调用这个组件时候,我们就可以使用@自定义事件去完成
九、自定义事件
在上面的案例当中,我们去破解数据流的单向的时候使用了自定义事件,其实在vue
开发里在,自定义事件是非常频繁的。现在我们把刚刚自定义的流程先梳理一下
现在我们通过一个最基本的案例来执行操作
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>自定义事件</title>
<style>
.btn {
min-width: 120px;
height: 35px;
border: none;
outline: none;
background-color: lightseagreen;
cursor: pointer;
color: white;
}
.btn:active {
box-shadow: 0px 0px 10px 5px lightgray;
}
</style>
</head>
<body>
<div id="app">
<my-button text="登陆" @click="aaa"></my-button>
</div>
<template id="temp1">
<div class="btn-box">
<button type="button" class="btn" @click="innerClick($event)">{{text}}</button>
</div>
</template>
</body>
<script src="./js/vue.js"></script>
<script>
let myButton = {
template: "#temp1",
props: {
text: {
type: String,
default: () => "按钮"
}
},
methods:{
innerClick(event){
this.$emit("click",event);
}
}
}
new Vue({
el: "#app",
methods: {
aaa() {
alert("hello world");
console.log(event);
}
},
components: {
myButton
}
})
</script>
</html>
十、组件的插槽
1. 普通插槽
也叫default默认插槽
组件中的插槽可以理解成我们之前的电脑里同的主板,这个主板可以确定大多数的功能,对于不确定的东西则预留一个插槽,方便后期插入东西进去
这个思维方式与组件很相似,我在封装组件的时候,其实也可以一些公共的确定的HTML封装起来,对于那些不确定的东西我们则给可以给它预留一下插槽
如果想在件组件的开始标签与结束标签中插入内容,则在封装组件进修必须预留插槽
如果要想在组件内预留插槽,则要使用<slot>
去完成,如下代码所示
<body>
<div id="app">
<one>
<button type="button">我是按钮</button>
<button type="button">我是按钮</button>
<button type="button">我是按钮</button>
</one>
</div>
<template id="temp1">
<div class="box">
<!-- 现在在这里预留了一个插口 -->
<slot></slot>
<h2>我是一个组件-------哈哈哈</h2>
</div>
</template>
</body>
<script src="./js/vue.js"></script>
<script>
let one = {
template:"#temp1"
}
new Vue({
el:"#app",
components:{
one
}
})
</script>
在上面的代码里面,
one
的虚拟dom里面插入了3个button标签,这3个标签最终全部插入到了slot
的位置
效果图:一个slot多次插入
同时还有另一种情况,一个插入点插入在多个插槽slot
里面,如下代码所示
<div id="app">
<one>
<button type="button">我是按钮</button>
</one>
</div>
<template id="temp1">
<div class="box">
<!-- 现在在这里预留了一个插口 -->
<slot></slot>
<h2>我是一个组件-------哈哈哈</h2>
<slot></slot>
</div>
</template>
在上面的代码里面,我们可以看到,一个
button
标签插入到了多个slot
里面
效果图:一个插入放在多个slot里面
在刚刚的代码里面,我们通过slot
在组件内部定义了插槽,后期就可以直接 插入,其实这些slot都叫默认插件,它的完整写法应该是下面的写法
默认情况所有的slot
都必须指定一个name的,如果不指定则默认就是default
2.具名插槽
在上面的默认插槽里面,所有的slot如果在不指定name的情况都是默认插槽default,但是这个name是可以设置为其它的值的,如果设置为其它的值 ,我们就把这个插槽叫具名插槽
<div id="app">
<one>
<p>我是默认的</p>
<button type="button" slot="footer">我是另一个插入的</button>
<a href="#" slot="inner">我是第三个插入的</a>
</one>
</div>
<template id="temp1">
<div class="box">
<slot name="footer"></slot>
<slot></slot>
<h2>我是一个组件-------哈哈哈
<slot name="inner"></slot>
</h2>
<slot name="footer"></slot>
</div>
</template>
在上面的代码里面,我们可以看到插槽是可以指定具体的名子的,仍然可以实现一对多,多对一
当插槽有了名子以后,我们在插入的时候就可以指定插入在某一个插槽里面
- 我们在组件里面定义插槽的时候使用了 标签,并且在这个
slot
标签上面定义了 name 这 个属性,这就是具名插槽- 在调用这个组件的时候,可以向指定的插槽位置插入内容,只需要在这个元素上面添加
slot="名称"
即可- 具名插槽也是可以多次使用的,所以在上面的代码当中
name="footer"
这个插槽就出现2 次
案例:现有下面的几种情况,同学想一下如何定义组件
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>插槽的案例</title>
<link rel="stylesheet" href="https://at.alicdn.com/t/font_2711195_ar2lbyi6qmj.css">
<style>
*{
margin: 0;
padding: 0;
list-style-type: none;
}
.page-title{
height: 45px;
background-color: #008de1;
font-weight: bold;
display: flex;
justify-content: center;
align-items: center;
color: white;
position: relative;
}
.left-back{
position: absolute;
left: 10px;
}
.right-menu{
position: absolute;
right: 10px;
}
.right-menu>.iconfont{
margin-left: 5px;
}
</style>
</head>
<body>
<div id="app">
<app-title :show-back="true" @back="bbb">
软帝点餐
<span slot="right-menu" class="iconfont icon-home-fill" @click="aaa"></span>
</app-title>
</div>
<template id="temp1">
<div class="page-title">
<div class="left-back" @click="backClick($event)" v-if="showBack">
<span class="iconfont icon-fanhui"></span>
返回
</div>
<slot></slot>
<div class="right-menu">
<slot name="right-menu"></slot>
</div>
</div>
</template>
</body>
<script src="./js/vue.js"></script>
<script>
let appTitle = {
template:"#temp1",
props:{
showBack:{
type:Boolean,
default:()=>false
}
},
methods:{
backClick(event){
this.$emit("back",event);
}
}
}
new Vue({
el:"#app",
components:{
appTitle
},
methods:{
aaa(){
alert("我是aaa的方法")
},
bbb(event){
alert("我是内部发射到外边的返回啊")
}
}
})
</script>
</html>
3. 作用域插槽
作用域插槽是一个比较常见的现象,在工作当中使用得非常频繁,现在来看一下场景
<body>
<div id="app">
<one>
<p>我想拿到组件内部的userName</p>
</one>
</div>
<template id="temp1">
<div class="box">
<h2>这是一个组件</h2>
<slot></slot>
</div>
</template>
</body>
<script src="./js/vue.js"></script>
<script>
let one = {
template: "#temp1",
data(){
return {
userName:"小珊子"
}
}
}
new Vue({
el: "#app",
components: {
one
}
})
</script>
我们以前的时候一直都是外部的数据传递到组件内部,如果把组件内部的数据传递到外边去呢,例如上面在上面的代码当中,如果把userName
传递到外边去
组件内部的数据如果要传递到组件外部去,其实原理是一样的,仍然使用自定义的属性
在上面的案例里面,我们看到了,我们通过slot-scope
拿到了插槽里在作用域,然后拿到了插槽上面所有的值,最后直接 使用
在上在贩案例里面,我们还看到了,这个些插槽都是默认的default
的插槽,如果是具名插槽呢?
<body>
<div id="app">
<one>
<div slot="default" slot-scope="scope">
用户名:{{scope.userName}}
</div>
<hr>
<div slot="footer" slot-scope="scope">
年龄:{{scope.age}}
</div>
</one>
</div>
<template id="temp1">
<div class="box">
<slot name="default" :user-name="userName"></slot>
<h2>这是一个组件</h2>
<slot name="footer" :age="18"></slot>
</div>
</template>
</body>
<script src="./js/vue.js"></script>
<script>
let one = {
template: "#temp1",
data(){
return {
userName:"小珊子"
}
}
}
new Vue({
el: "#app",
components: {
one
}
})
</script>
代码分析:
- 在
slot="default"
的插槽里面,我们的数据是直接向外部传送了userName
,同时要注意default
是默认的插槽,可以不指定名子也行- 在
slot="footer"
的插槽里面,我们向外传递数据:age='18'
- 在调用这个组件的时候,我们可以通过
slot="default"
或slot="footer"
来决定到底插入在什么地方,同时还可以使用slot-scope
拿到这个插槽的作用域(通俗一点讲就是拿到这个插槽向外部传递的数据)- 主要作用的还是两个属性
slot
决定到底插入在什么位置slot-scope
拿到作用域(拿到插槽上面的数据)
上面的slot-scope
是过时的写法,现在新的vue2.6.1开始就使用新的语法叫v-slot
了
<template id="temp1">
<div class="box">
<slot name="default" :user-name="userName"></slot>
<h2>这是一个组件</h2>
<slot name="footer" :age="18"></slot>
</div>
</template>
旧版本的语法
<one>
<p slot="default" slot-scope="scope">
用户名:{{scope.userName}}
</p>
<p slot="footer" slot-scope="scope">
年龄:{{scope.age}}
</p>
</one>
新版本的语法
<one>
<template v-slot:default="scope">
<p>
用户名:{{scope.userName}}
</p>
</template>
<template v-slot:footer="scope">
<p>
年龄:{{scope.age}}
</p>
</template>
</one>
代码分析:
v-slot
就是去拿作用域,如果后面不跟具体的名称就是拿default
,所以v-slot:default
就是v-slot
- 如果要拿作用域则
v-slot:插槽名称="scope"
- 新版本里面只能通过
<template>
要都会来插入,不能通过其它的标签
最新版本的语法
<one>
<template #default="scope">
<h1>我在默认的位置插入:{{scope.userName}}</h1>
</template>
<template #footer="scope">
<p>我在footer的位置插入:{{scope.age}}</p>
</template>
</one>
我们推荐同学们使用第一种和第三种,但是还是要了解第二种
案例
<div id="app">
<one>
<template #default="{age,sex,userName}">
<h2>插入</h2>
<p>{{age}}</p>
<p>{{userName}}</p>
<p>{{sex}}</p>
</template>
</one>
</div>
<template id="temp1">
<div class="box">
<h2>这是一个组件</h2>
<slot name="default" :age="18" :user-name="userName" sex="女"></slot>
</div>
</template>
在新版本的语法里面,作用域是可以直接解构的
十一、vue及组件生命周期
我们都知道如果要使用vue ,则在最开始的时候就要去new
一个Vue,那么在创建这个vue的对象过程当中,到底经过了哪些过程呢,这个我们就必须要掌握vue的生命周期
生命周期( lifeCycle):指从出生到死亡的过程,vue的生命周期指的是从创建到销毁的过程
在vue的内部,它的生命其实是有四个阶段
通俗一点来讲,我们把vue的生命周期分为4个阶段,8个过程,它们在不同的状态有不同的操作,在每个阶段都会执行一个特殊的函数,这个函数叫钩子函数
钩子函数:所谓的钩子函数指的是vue在不同的周期情况下自己调用的函数
beforeCreate
vue创建之前created
vue创建以后beforeMount
vue挂载之前【vue接管页面之前】mounte
vue挂载之后【vue接管页面之后】beforeUpdate
vue内部更新之前updated
vue内部更新以后beforeDestory
vue销毁之前destoryed
vue销毁之后
vue在一个是8个状态,对应了8个钩子函数,在不同钩子函数里面要执行不同的操作
周期名称 | data属性 | computed属性 | methods方法 | $refs操作 |
---|---|---|---|---|
beforeCreate() | 否 | 否 | 否 | 否 |
created() | 是 | 是 | 是 | 否 |
beforeMount() | 是 | 是 | 是 | 否 |
mounted() | 是 | 是 | 是 | 是 |
在vue的组件内部也是这8个生命周期钩子函数,之前就给同学们讲过,组件就是一个小型的vue
1. 跨生命周期的调用
vue应该是在不同的生命周期里面执行不同的操作,所以有严格的限制条件,但是某些特殊情况下我们是需要跨生命周期调用的,这咋办呢。如下案例所示
<body>
<div id="app">
<h2 ref="h2"></h2>
</div>
</body>
<script src="./js/vue.min.js"></script>
<script>
new Vue({
el: "#app",
data: {
age: 18
},
created() {
console.log("我是created");
//将原来的年龄加上一个20以内的随机数,然后设置在h2上面,但是要通过dom操作
let newAge = this.age + parseInt(Math.random() * 20);
this.$refs.h2.innerText = newAge;
},
mounted() {
}
});
</script>
在上面的代码当中,我们可以看到,我们在
created()
生命周期里面创建了一个变量,然后要放在ref
为h2的元素上面,怎么办问题1:我不能在
created()
里面操作$refs
问题2:如果我在
mounted()
操作$refs
这个时候又拿到不变量newAge
解决方案如下
new Vue({
el: "#app",
data: {
age: 18
},
created() {
console.log("我是created");
//将原来的年龄加上一个20以内的随机数,然后设置在h2上面,但是要通过dom操作
let newAge = this.age + parseInt(Math.random() * 20);
this.$nextTick(()=>{
this.$refs.h2.innerText = newAge;
});
},
mounted() {
}
});
我们把需要在下一个生命周期执行的代码放在了
$nextTick
里面
$nextTick()
官方的解释这样的,它会判断当前的代码是否可以在本生命周期执行,如果不可以会顺延到下一个生命周期,如果还不行则继续顺延,直到可以执行为止
2. v-if及v-show的区别
之前在讲vue的基础的时候我们提到了一点,v-if
是通过注释的方式让元素消失了,而v-show
则是通过css属性display:none
来操作的,但是它的本质点在生命周期里面
v-show不会重新触发生命周期,因为本质上面是把元素隐藏了,没有销毁
<body>
<div id="app">
<button type="button" @click="flag = !flag">切换</button>
<!--这里使用了v-show的操作 -->
<one v-show="flag"></one>
</div>
<template id="temp1">
<div>
<h2>我是一个数:{{num}}</h2>
</div>
</template>
</body>
<script src="./js/vue.min.js"></script>
<script>
let one = {
template:"#temp1",
data(){
return {
num:18
}
},
created(){
console.log("我是组件内部的created"+new Date().toLocaleString())
}
}
new Vue({
el: "#app",
data:{
flag:true
},
components:{
one
}
});
</script>
我们同样的,把上面的代码替换成v-if
来试一下
<one v-if="flag"></one>
这个时候我们发现,每次重新显示的时候,生命周期都重新执行了,这说明每次重新显示都是重新创建了一个新的组件
3. keep-alive的使用
vue及所有的组件正常情况下都要经历上面的四个过程,从创建到销毁,但是有一个特殊情况,主是这个组件吃了长生不死药,它不死,对于这种情况,我们叫上keep-alive
在vue的内部,有一个自带的组件叫 <keep-alive>
在这个组件下面的内容是不会被销毁掉的,也就是不 会执行 destory 这个过程
<body>
<div id="app">
<button type="button" @click="flag = !flag">切换</button>
<keep-alive>
<one v-if="flag"></one>
</keep-alive>
</div>
<template id="temp1">
<div>
<h2>我是一个数:{{num}}</h2>
</div>
</template>
</body>
<script src="./js/vue.min.js"></script>
<script>
let one = {
template:"#temp1",
data(){
return {
num:18
}
},
created(){
console.log("我是组件内部的created"+new Date().toLocaleString())
},
activated(){
console.log("activated,我被激活显示了");
},
deactivated(){
console.log("deactivated,我又被隐藏了");
}
}
new Vue({
el: "#app",
data:{
flag:true
},
components:{
one
}
});
</script>
代码分析:
在上面的代码里同,因为
one
的组件是被<keep-alive>
,即使我使用了v-if
想去销毁它,这也不行,它不会被销毁
当一个组件如果被 keep-alive
以后,它就不会销毁,但是又多了两个特殊的生命周期钩子函数
activated
当激活【显示】的时候触发deactivated
当休眠【隐藏】的时候触发
4. vue1.0与2.0生命周期对比
5. 生命周期图
关于上面的那个
$mount
的过程,大家可以参考下面的代码let vm = new Vue({ data:{ age:18 }, computed:{ userName(){ return "帅哥"; } }, methods:{ sayHello(){ console.log("我是一个方法") } }, }); vm.$mount("#app");
十二、关于组件的属性传值注意事项
在组件内部如果要进行传值 ,我们推荐使用自定义属性传值 ,如下所示
<one userName="张三"></one>
<!--向组件内部传递了一个属性,userName,值为"张三" -->
<one age="18"></one>
<!-- 向组件内部传递了一个属性 age,值为"18",请注意是字符串的18 -->
<one :age="18"></one>
<!-- 这代表向组件内部传递一个属性age,值为18,这里是数据类型的18 -->
<one show-back="true"></one>
<!-- 这代表向件内部传递showBack,值为字符串的"true" -->
<one :show-back="true"></one>
<!-- 这传递的就是布尔类型的true -->
易错点
<one show-back="false"></one>
<!--内部如果想接收布尔类型,但是我们传递了字符串"false",在转换的时候Boolean("false")结果是true -->
自定义属性在传递数据的时候默认都是字符中,如果是其它 的数据类型,请使用动态绑定
<one :show-back="false"></one>
评论区