一、Vue
知识点串讲
复习一下Vue
中的核心知识点。
复习完基本的知识点以后,后面再来看一下其它的面试内容
1、基本使用
下面,先来看一段最简单的代码,如下所示:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vue基本使用</title>
</head>
<body>
<div id="app">
{
{msg}}
</div>
<script src="vue.js"></script>
<script>
//创建vue实例
const app = new Vue({
el: "#app",
data() {
return {
msg: "hello world",
};
},
});
setTimeout(() => {
app.msg = "hello Vue";
}, 1000);
</script>
</body>
</html>
在上面的代码中,创建了vue
的实例,并且指定了数据,最终数据展示在id
为app
的这个div
中,并且在停顿了1秒中以后,通过Vue
的实例来修改对应的msg
数据。
通过上面的代码,我们能够够看到Vue
的核心理念是数据驱动的理念,所谓的数据驱动的理念:当数据发生变化的时候,用户界面也会发生相应的变化,开发者并不需要手动的去修改dom
.
简单的理解:就是vue.js
帮我们封装了数据和dom
对象操作的映射,我们只需要关心数据的逻辑处理,数据的变化就能够自然的通知页面进行页面的重新渲染。
这样做给我们带来的好处就是,我们不需要在代码中去频繁的操作dom
了,这样提高了开发的效率,同时也避免了在操作Dom
的时候出现的错误。
Vue.js
的数据驱动是通过MVVM
这种框架来实现的,MVVM
框架主要包含三部分:Model
,View
,ViewMode
Model
:指的是数据部分,对应到前端就是JavaScript
对象。
View
:指的就是视图部分
ViewModel
: 就是连接视图与数据的中间件(中间桥梁)
以上三部分对应到代码中的位置如下图所示:
下面,我们再来看一张图来理解一下MVVM
框架的作用:
数据(Model
)和视图(View
)是不能直接通讯的,而是需要通过ViewModel
来实现双方的通讯。当数据(Model
)变化的时候,ViewModel
能够监听到这种变化,并及时通知View
视图做出修改。同样的,当页面有事件触发的时候,ViewModel
也能够监听到事件,并通知数据(Model
)进行响应。所以ViewModel
就相当于一个观察者,监控着双方的动作,并及时通知对方进行相应的操作。
简单的理解就是:MVVM
实现了将业务(数据)与视图进行分离的功能。
在这里还需要注意的一点就是:
MVVM
框架的三要素:响应式,模板引擎,渲染
响应式:vue
如何监听数据的变化?
模板:Vue
的模板如何编写和解析?怎样将具体的值替换掉{
{msg}}
内容,这就是模板引擎的解析。
渲染:Vue
如何将模板转换成html
? 其实就是有虚拟DOM
向真实DOM
的转换。
在后面,我们还会深入探讨这块内容,包括我们自己模拟实现一个数据驱动的框架。
以上内容也是面试的时候,会问到的问题。
2、模板语法
2.1 属性绑定
属性的绑定,下面先来看一下关于对属性的绑定
<div id="app">
<h2 v-bind:title="msg">
{
{msg}}
</h2>
</div>
在上面的代码中,我们通过v-bind
的方式给h2
绑定了一个title
属性。
当然,上面的代码我们也可以使用如下的方式来进行简化
<div id="app">
<h2 :title="msg">
{
{msg}}
</h2>
</div>
为了避免闪烁的问题,也就是最开始的时候,出现:{
{msg}}
的情况,可以使用如下的绑定方式。
<div id="app">
<h2 :title="msg">
<!-- {
{msg}} -->
<span v-text="msg"></span>
</h2>
</div>
3、 列表渲染
我们可以使用v-for
指令基于一个数组来渲染一个列表.v-for
指令需要使用item in items
形式的语法。其中items
是源数组,而item
则是被迭代的数组元素的别名。
基本实现的代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>列表渲染</title>
</head>
<body>
<div id="app">
<ul>
<!-- users表示数组,item表示从数组中取出的对象,这个名字可以随意取 -->
<!-- 注意 v-for必须结合key属性来使用,它会唯一标识数组中的每一项,未来当数组中的那一项改变的时候,它会只更新那一项,好处就是提升性能。注意key的值唯一,不能重复 -->
<!-- index表示数组的索引值,该名字可以随意定义 -->
<li v-for="(item,index) in users" :key="item.id">
编号:{
{item.id}} 姓名:{
{item.name}}---索引:{
{index}}
</li>
</ul>
</div>
<script src="vue.js"></script>
<script>
new Vue({
el: "#app",
data: {
users: [
{
id: 1,
name: "张三",
},
{
id: 2,
name: "李四",
},
{
id: 3,
name: "老王",
},
],
},
});
</script>
</body>
</html>
注意:为了能够保证列表渲染的性能,我们需要给v-for
添加key
属性。key
值必须唯一,而且不能使用index
与random
作为key
的值。
关于这一点是与虚拟DOM
算法密切相关的。在后面的课程中会最为一个重点来探讨虚拟DOM
的内容。这也是面试的时候经常被问到的问题。
4、v-model
在前面讲解vue
简介的时候,我们说过,如果model
中的数据发生了改变,会通过ViewModel
通知View
更新数据,这个效果前面我们也已经演示了,现在演示一下当view
中的数据发生了变化后,怎样通过ViewModel
来通知model来完成数据的更新。
其实这就是我们常说的,“双向数据绑定”
怎样实现这种效果呢?可以通过v-model来实现。
<!-- v-model指令用来双向数据绑定:就是model和view中的值进行同步变化 -->
<!-- v-model只能在input/textarea/selet 也就是表单元素-->
具体代码实现如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>双向数据绑定</title>
<script src="./vue.js"></script>
</head>
<body>
<div id="app">
<input type="text" v-model="userName" />
</div>
<script>
const vm = new Vue({
el: "#app",
data: {
userName: "zhangsan",
},
});
</script>
</body>
</html>
怎样验证v-model实现了双向数据绑定呢?
可以打开控制台,然后输入:vm.userName
发现输出的值为"zhangsan",
取的是模型中的数据。
当在文本框中输入新的值后,在敲一下vm.userName
发现对应的数据发生了变化,也就是视图中的数据发生了变化,模型中的数据也 会发生变化。
那么在控制台中直接给vm.userName="lisi",
发现文本框中的值也发生了变化。
关于v-model
这个知识点,面试的时候经常会被问到的一个问题就是,自己能否模拟实现一个类似于v-model
的双向数据绑定的效果。关于这个问题你可以先思考一下,在后面的课程中,我们会详细的讲解。
5、v-on
怎样监听dom
的事件呢?可以通过v-on
指令完成,具体的代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="./vue.js"></script>
</head>
<body>
<div id="app">
<span>{
{name}}</span>
<!-- 通过v-on来指定对应的事件,然后后面跟上对应的方法名,方法的定义在methods完成 -->
<button v-on:click="changeName">更换姓名</button>
</div>
<script>
var vm = new new Vue({
el: '#app',
data: {
name: 'zhangsan'
},
// 通过methods完成函数或方法的定义
methods: {
changeName() {
// 在methods中要获取data中的属性,需要通过this来完成,this表示的是vue实例。
this.name = "itcast"
}
}
})
</script>
</body>
</html>
还可以通过简写的形式。建议以后都使用简写的形式
<button @click="changeName">更换姓名</button>
带参数的形式如下:
<button @click="changeNameByArg('laowang')">带参数的情况</button>
<script>
var vm = new new Vue({
el: '#app',
data: {
name: 'zhangsan'
},
// 通过methods完成函数或方法的定义
methods: {
changeName() {
// 在methods中要获取data中的属性,需要通过this来完成,this表示的是vue实例。
this.name = "itcast"
},
changeNameByArg(userName) {
this.name = userName
}
}
})
</script>
除了绑定鼠标的单击事件以外,也可以绑定键盘的事件。
例如,页面有有一个文本框,用户在该文本框中输入内容,按下回车键,获取到用户输入的内容。
<div id="app">
<span>{
{name}}</span>
<!-- 通过v-on来指定对应的事件,然后后面跟上对应的方法名,方法的定义在methods完成 -->
<button @click="changeName">更换姓名</button>
<button @click="changeNameByArg('laowang')">带参数的情况</button>
<!--给文本框添加键盘事件-->
<input type="text" @keydown.enter="changeUserName" v-model="name" />
</div>
在mehtods
中定义changeUserName
方法
// 通过methods完成函数或方法的定义
methods: {
changeName() {
// 在methods中要获取data中的属性,需要通过this来完成,this表示的是vue实例。
this.name = "itcast";
},
changeNameByArg(userName) {
this.name = userName;
},
//定义处理文本框键盘事件的方法。
changeUserName() {
console.log(this.name);
},
},
在上面的案例中,我们使用了按键的修饰符:.enter
,在官方文档中,还有其它的按键修饰符,如下所示:
https://cn.vuejs.org/v2/guide/events.html#%E6%8C%89%E9%94%AE%E4%BF%AE%E9%A5%B0%E7%AC%A6
与之相关的就是事件修饰符,如下所示:
https://cn.vuejs.org/v2/guide/events.html#%E4%BA%8B%E4%BB%B6%E4%BF%AE%E9%A5%B0%E7%AC%A6
以上内容,大家可以在课下的时候,仔细看一下。
6、Class与Style绑定
这块主要内容主要与样式设置有关。
操作元素的 class
列表和内联样式是数据绑定的一个常见需求。因为它们都是 attribute
,所以我们可以用 v-bind
处理它们:只需要通过表达式计算出字符串结果即可。不过,字符串拼接麻烦且易错。因此,在将 v-bind
用于 class
和 style
时,Vue.js
做了专门的增强。表达式结果的类型除了字符串之外,还可以是对象或数组。
下面先来看一下Class
的绑定。
在"列表渲染"中给每个列表项添加对应的样式。
<style>
.actived {
background-color: #dddddd;
}
</style>
下面给li
列表添加上面所定义的样式。
<li
v-for="(item,index) in users"
:key="item.id"
:class="{actived:true}"
>
编号:{
{item.id}} 姓名:{
{item.name}}---索引:{
{index}}
</li>
在上面的代码中,我们可以看到,给li
标签绑定了class
属性,同时actived
的值为true
,表示给li
添加actived
样式。
现在有一个需求,就是当鼠标移动到列表项上的时候,更改对应的背景色。
<li
v-for="(item,index) in users"
:key="item.id"
:class="{actived:selectItem===item}"
@mousemove="selectItem=item"
>
在对class
进行绑定的时候,做了一个判断,判断一下selectItem
是否与item
相等,如果相等添加样式。
当鼠标移动到某个li
列表上的时候,触发mousemove
事件,将item
的值给selectItem
.
在data
中定义selectItem
.
如下所示:
data: {
selectItem: "",
users: [
{
id: 1,
name: "张三",
},
{
id: 2,
name: "李四",
},
{
id: 3,
name: "老王",
},
],
},
完整 代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>列表渲染</title>
<style>
.actived {
background-color: #dddddd;
}
</style>
</head>
<body>
<div id="app">
<ul>
<!-- users表示数组,item表示从数组中取出的对象,这个名字可以随意取 -->
<!-- 注意 v-for必须结合key属性来使用,它会唯一标识数组中的每一项,
未来当数组中的那一项改变的时候,它会只更新那一项,好处就是提升性能。
注意key的值唯一,不能重复 -->
<!-- index表示数组的索引值,该名字可以随意定义 -->
<li
v-for="(item,index) in users"
:key="item.id"
:class="{actived:selectItem===item}"
@mousemove="selectItem=item"
>
编号:{
{item.id}} 姓名:{
{item.name}}---索引:{
{index}}
</li>
</ul>
</div>
<script src="vue.js"></script>
<script>
new Vue({
el: "#app",
data: {
selectItem: "",
users: [
{
id: 1,
name: "张三",
},
{
id: 2,
name: "李四",
},
{
id: 3,
name: "老王",
},
],
},
});
</script>
</body>
</html>
下面,我们再来看一下Style
的绑定。
<li
v-for="(item,index) in users"
:key="item.id"
:style="{backgroundColor:selectItem===item?'#dddddd':'transparent'}"
@mousemove="selectItem=item"
>
编号:{
{item.id}} 姓名:{
{item.name}}---索引:{
{index}}
</li>
通过上面的代码,可以看到通过绑定style
的方式来处理样式是非常麻烦的。
7、条件渲染
v-if和v-show指令可以用来控制元素的显示和隐藏
下面,我们先来看一下v-if
的应用。
这里还是对用户数据进行判断。
<div id="app">
<p v-if="users.length===0">没有任何用户数据</p>
<ul v-else>
<!-- users表示数组,item表示从数组中取出的对象,这个名字可以随意取 -->
<!-- 注意 v-for必须结合key属性来使用,它会唯一标识数组中的每一项,
未来当数组中的那一项改变的时候,它会只更新那一项,好处就是提升性能。
注意key的值唯一,不能重复 -->
<!-- index表示数组的索引值,该名字可以随意定义 -->
<!-- <li
v-for="(item,index) in users"
:key="item.id"
:class="{actived:selectItem===item}"
@mousemove="selectItem=item"
>
编号:{
{item.id}} 姓名:{
{item.name}}---索引:{
{index}}
</li> -->
<li
v-for="(item,index) in users"
:key="item.id"
:style="{backgroundColor:selectItem===item?'#dddddd':'transparent'}"
@mousemove="selectItem=item"
>
编号:{
{item.id}} 姓名:{
{item.name}}---索引:{
{index}}
</li>
</ul>
</div>
在上面的代码中,我们首先对users
数组做了一个判断,如果没有数据,就在页面上展示:“没有任何用户数据”
否则渲染整个列表。
上面是关于v-if
的使用,下面看一下v-show
.
v-show
是通过css
属性display
控制元素显示,元素总是存在的。
v-if
:通过控制dom
来控制元素的显示和隐藏,如果一开始条件为false
,元素是不存在的。
什么时候使用v-show
,什么时候使用v-if
呢?
如果需要频繁的控制元素的显示与隐藏,建议使用v-show
. 从而避免大量DOM
操作,提高性能。
而如果某个元素满足条件后,渲染到页面中,并且以后变化比较少,可以使用v-if
8、计算属性
计算属性出现的目的是解决模板中放入过多的逻辑会让模板过重且难以维护的问题.
计算属性是根据data中已有的属性,计算得到一个新的属性.
下面,我们可以通过一个案例来学习一下计算属性、
在一个文本框中输入第一个名字,第二个文本框中输入第二个名字,然后展示全部名称。
<body>
<div id="app">
<input type="text" v-model="firstName">
<input type="text" v-model="lastName">
<!-- 这样是模板逻辑变得非常复杂,不易维护 -->
<div>全名:{
{firstName + lastName}}</div>
<div>全名:{
{fullName}}</div>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
firstName: '',
lastName: ''
},
// 创建计算属性通过computed关键字,它是一个对象
computed: {
// 这里fullName就是一个计算属性,它是一个函数,但这个函数可以当成属性来使用
fullName() {
return this.firstName + this.lastName
}
}
})
</script>
</body>
了解了计算属性后,下面对用户列表添加一个功能,要求是计算总人数。
可以在ul
列表下面,添加如下的代码。
<p>
总人数:{
{users.length+"个"}}
</p>
最终展示出了,对应的人数,但是这里在模板中做了运算(在这里做了字符串拼接,虽然计算简单,但是最好还是通过计算属性来完成),为了防止在模板中放入过多的逻辑计算,这里可以使用计算属性来解决。
下面对代码进行改造:
<p>
<!-- 总人数:{
{users.length+"个"}} -->
总人数:{
{total}}
</p>
计算属性实现:
<script>
new Vue({
el: "#app",
data: {
selectItem: "",
users: [
{
id: 1,
name: "张三",
},
{
id: 2,
name: "李四",
},
{
id: 3,
name: "老王",
},
],
},
computed: {
total() {
// 计算属性是有缓存性:如果值没有发生变化,则页面不会重新渲染
return this.users.length + "个";
},
},
});
</script>v
通过上面的代码,可以看到使用计算属性,让界面变得更加的简洁。
使用计算属性还有一个好处:
其实细心的话就会发现,调用methods里的方法也能实现和计算属性一样的效果,既然使用methods就可以实现,那为什么还需要计算属性呢?原因就是计算属性是基于他的依赖缓存的(所依赖的还是data
中的数据)。一个计算属性所依赖的数据发生变化时,他才会重新取值
也就是说:只要相关依赖没有改变,对此访问计算属性得到的是之前缓 存的结果,不会多次执行。
下面我们测试一下:
<p>
<!-- 总人数:{
{users.length+"个"}} -->
总人数:{
{total}} 总人数:{
{total}}
</p>
在上面的代码中,我们使用total
了两次。
下面在看一下关于计算属性中的代码修改:
computed: {
total() {
console.log("aaa");
// 计算属性是有缓存性:如果值没有发生变化,则页面不会重新渲染
return this.users.length + "个";
},
},
这里,我们通过console
输出字符串aaa
,但是在控制台上只是输出了一次,因为,第二次使用total
的时候,发现值没有变化,所以直接从缓存中获取了对应的值。并没有重新进行计算,这样带来的好处就是,性能得到了提升。
下面,我们换成methods
函数的形式来看一下:
<p>
<!-- 总人数:{
{users.length+"个"}} -->
总人数:{
{total}} 总人数:{
{total}} 总人数:{
{getTotal()}}
总人数:{
{getTotal()}}
</p>
在上面的代码中,调用了两次getTotal
方法。
getTotal
方法的实现如下:
methods: {
getTotal: function () {
console.log("methods");
return this.users.length + "个";
},
},
实现的方式是差不多的,但是这里却执行了两次。(注意:由于本案例中给每一个li
标签添加了*@mousemove*
,所以只要鼠标移动到列表上,就会导致页面重新渲染,这时会不断的调用getTotal
方法。)
所以通过上面案例的演示,可以明确的看出计算属性是有缓存的,也就是所依赖的data
属性中的数据没有变化,那么是不会重新计算的。所以提升了对应的性能。
所以说,在进行大量耗时计算的时候,建议使用计算属性来完成。
如下代码:
data: {
selectItem: "",
num: 100
}
在data
中定义了num
属性,并且初始值为100、
下面在计算属性中进行求和的运算,代码实现如下:
computed: {
total() {
console.log("aaa");
// 计算属性是有缓存性:如果值没有发生变化,则页面不会重新渲染
// return this.users.length + "个";
let count = 0;
for (let i = 0; i <= this.num; i++) {
count += i;
}
return count;
},
},
通过演示,可以发现计算属性只是在第一次调用的时候,执行了一次,后续由于所依赖的数据num
没有发生变化,所以即时调用多次,也并没有重新进行计算,而是获取上次计算的结果,所以说在进行大量耗时计算的时候,通过计算属性可以提升性能。
9、侦听器
侦听器就是侦听data
中的数据变化,如果数据一旦发生变化就通知侦听器所绑定方法,来执行相应的操作。从这一点上,与计算属性是非常类似的。
但是,侦听器也有自己独有的应用场景。
执行异步或开销较大的操作。
下面,先来看一下侦听器的基本使用
我们使用侦听器来统计总人数。
<p>
总人数:{
{totalCount}}
</p>
在data
中定义totalCount
属性。
data: {
selectItem: "",
num: 100,
totalCount: 0
}
使用watch
来监听users
数组的数据变化。
watch: {
users: {
immediate: true, //立即执行
handler(newValue, oldValue) {
this.totalCount = newValue.length + "个人";
},
},
}
当users
数组发生了变化后,就会执行handler
这个函数,同时用于加上了immediate
属性,并且该属性的值为true
,表示的就是在初始化绑定的时候,也会去执行侦听器。因为watch
在初始化绑定的时候是不会执行的,等到所监听的内容改变之后才会去侦听执行。
以上就是watch
侦听器的基本使用,但是通过这个案例,我们发现还是使用计算属性来统计总人数更加的方便一些。
当然,侦听器有自己的应用场景,它的应用场景就是在执行异步请求或者进行开销比较大的操作的时候,会使用侦听器。
下面我们在通过一个案例,来体会一下watch
侦听器的应用场景。
下面我们来看一个异步操作的情况。就是当用户在一个文本框中输入了用户名以后,要将输入的用户名发送到服务端,来检查该用户名是否已经被占用。
具体的实现代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>侦听器</title>
</head>
<body>
<div id="app">
<div>
<span>用户名</span>
<!--这里使用了lazy,保证当文本框失去焦点后,才去执行对应操作-->
<span><input type="text" v-model.lazy="uname" /></span>
<span>{
{message}}</span>
</div>
</div>
<script src="./vue.js"></script>
<script>
const vm = new Vue({
el: "#app",
data: {
uname: "",
message: "",
},
methods: {
checkUserName: function (userName) {
let that = this;
setTimeout(function () {
if (userName === "admin") {
that.message = "用户名已经存在,请更改....";
} else {
that.message = "该用户名可以使用.....";
}
}, 3000);
},
},
watch: {
uname: function (value) {
//调用后台接口,来验证用户名是被占用
this.checkUserName(value);
this.message = "正在校验用户名....";
},
},
});
</script>
</body>
</html>
以上的案例,就是通过watch
来监听uname
的值是否发生变化,如果发生了变化,就通过发送异步请求来检查uname
中的值,是否已经被占用。
通过以上的案例:我们可以看到watch
是允许异步操作的,并且在我们得到最终的结果前,可以设置中间状态,这些都是计算属性无法做到的。
最后我们把计算属性与侦听器做一个总结,看一下它们的应用场景。
第一点:语境上的差异:
watch
适合一个值发生了变化,对应的要做一些其它的事情,适合一个值影响多个值的情形。
例如,上面案例中的用户名检测,这里是一个uname
发生了变化,但是这里做了很多其它的事情,例如修改message
的值,发送异步请求。
而计算属性computed
:一个值由其它的值得来,其它值发生了变化,对应的值也会变化,适合做多个值影响一个值的情形。
例如如下代码:
computed:{
fullName(){
return this.firstName+' '+this.lastName
}
}
第二点:计算属性有缓存性。
由于这个特点,我们在实际的应用中,能用计算属性的,会首先考虑先使用计算属性。
第三点:侦听器选项提供了更加通用的方法,适合执行异步操作或者较大开销操作。
10、生命周期简介
每个Vue
实例在被创建时都要经过一系列的初始化过程,例如:需要设置数据的监听,编译模板,将实例挂载到DOM
上,并且在数据变化时更新DOM
等,这些过程统称为Vue
实例的生命周期
。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。
下面,我们来看一下这些钩子函数的应用。
通过一个异步获取列表数据的案例,来查看这些生命周期的钩子函数应用。
在这里是通过异步的方式获取用户列表的数据。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>列表渲染</title>
<style>
.actived {
background-color: #dddddd;
}
</style>
</head>
<body>
<div id="app">
<p v-if="users.length===0">没有任何用户数据</p>
<ul v-else>
<!-- users表示数组,item表示从数组中取出的对象,这个名字可以随意取 -->
<!-- 注意 v-for必须结合key属性来使用,它会唯一标识数组中的每一项,未来当数组中的那一项改变的时候,它会只更新那一项,好处就是提升性能。注意key的值唯一,不能重复 -->
<!-- index表示数组的索引值,该名字可以随意定义 -->
<!-- <li
v-for="(item,index) in users"
:key="item.id"
:class="{actived:selectItem===item}"
@mousemove="selectItem=item"
>
编号:{
{item.id}} 姓名:{
{item.name}}---索引:{
{index}}
</li> -->
<li
v-for="(item,index) in users"
:key="item.id"
:style="{backgroundColor:selectItem===item?'#dddddd':'transparent'}"
@mousemove="selectItem=item"
>
编号:{
{item.id}} 姓名:{
{item.name}}---索引:{
{index}}
</li>
</ul>
<p>
<!-- 总人数:{
{users.length+"个"}} -->
<!-- 总人数:{
{total}} 总人数:{
{total}} 总人数:{
{getTotal()}}
总人数:{
{getTotal()}} -->
总人数:{
{totalCount}}
</p>
</div>
<script src="vue.js"></script>
<script>
new Vue({
el: "#app",
data: {
selectItem: "",
num: 100,
totalCount: 0,
//指定users默认数据为一个空数组。
users: [],
},
//组件实例已创建时,执行created方法,来调用getUserList方法,发送异步请求获取数据
//将获取到的数据交个users这个状态数组。
async created() {
const users = await this.getUserList();
this.users = users;
},
methods: {
getTotal: function () {
console.log("methods");
return this.users.length + "个";
},
//在getUserList方法中,模拟一个异步请求。
getUserList: function () {
return new Promise((resolve) => {
setTimeout(() => {
resolve([
{
id: 1,
name: "张三",
},
{
id: 2,
name: "李四",
},
{
id: 3,
name: "老王",
},
]);
}, 2000);
});
},
},
watch: {
users: {
immediate: true, //立即执行
handler(newValue, oldValue) {
this.totalCount = newValue.length + "个人";
},
},
},
// computed: {
// total() {
// console.log("aaa");
// // 计算属性是有缓存性:如果值没有发生变化,则页面不会重新渲染
// // return this.users.length + "个";
// let count = 0;
// for (let i = 0; i <= this.num; i++) {
// count += i;
// }
// return count;
// },
// },
});
</script>
</body>
</html>
上面的代码,还是对原有的“列表渲染”内容进行更改。
第一:将users
的值定义为空数组
第二:定义getUserList
方法,在该方法中模拟异步操作,最终返回的是一个Promise
对象。
第三:在created
阶段调用getUserList
方法来获取数据,将获取到的数据赋值给users
这个状态数组,注意这里需要将created
修改成async
与await
的形式。同时还要注意created
的执行时机:组件实例已创建时,执行created方法。
现在已经对生命周期有了一个简单的了解,下面我们继续探讨生命周期的内容。
11、生命周期探讨
在这一小节中,我们看一下vue
生命周期中其它的一些钩子函数内容。
其实Vue
实例的生命周期,主要分为三个阶段,分别为
- 挂载(初始化相关属性,例如
watch
属性,method
属性)beforeCreate
created
beforeMount
mounted
- 更新(元素或组件的变更操作)
beforeUpdate
updated
- 销毁(销毁相关属性)
beforeDestroy
destroyed
下面,我们再来看一道面试题:
关于Vue的生命周期,下列哪项是不正确的?()[单选题]
A、Vue 实例从创建到销毁的过程,就是生命周期。
B、页面首次加载会触发beforeCreate, created, beforeMount, mounted, beforeUpdate, updated。
C、created表示完成数据观测,属性和方法的运算,初始化事件,$el属性还没有显示出来。
D、DOM渲染在mounted中就已经完成了。
分析:
选项A
是没有问题的,Vue
实例从创建到销毁的过程就是生命周期。
关于B
选项,我们可以通过写一个程序来进行验证。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>生命周期</title>
</head>
<body>
<div id="app">{
{foo}}</div>
<script src="./vue.js"></script>
<script>
const vm = new Vue({
el: "#app",
data: {
foo: "foo",
},
beforeCreate() {
console.log("beforCreate");
},
created() {
console.log("created");
},
beforeMount() {
console.log("beforeMount");
},
mounted() {
console.log("mounted");
},
beforeUpdate() {
console.log("beforeUpdate");
},
updated() {
console.log("updated");
},
beforeDestroy() {
console.log("beforeDestroy");
},
destroyed() {
console.log("destroyed");
},
});
</script>
<script></script>
</body>
</html>
在上面的代码中,我们将所有的钩子函数都添加上了,然后打开浏览器,看下执行结果:
beforCreate
created
beforeMount
mounted
以上就是初次加载时所执行的钩子函数,并没有beforeUpdate
与updated
,所以选项B
是错误的。
那么beforeUpdate
与updated
什么时候会执行呢?是在,组件或者是元素更新的时候。
下面,我们来测试一下,看一下效果。
首先增加一个"更新"按钮
<div id="app">
{
{foo}}
<button @click="update">更新</button>
</div>
对应的update
方法的实现如下:
methods: {
update: function () {
this.foo = "hello";
},
},
在update
方法中,修改了foo
属性的值。打开浏览器,单击“更新”按钮后,看到的效果如下:
beforeUpdate
updated
通过以上的测试,可以验证在更新元素的时候,会执行在“更新”阶段的钩子函数。
下面,我们在测试一下,看一下“销毁”阶段的钩子函数的执行。
<div id="app">
{
{foo}}
<button @click="update">更新</button>
<button @click="destroy">销毁</button>
</div>
在上面的代码中增加了一个销毁的按钮,对应的destroy
方法的实现如下:
methods: {
update: function () {
this.foo = "hello";
},
destroy: function () {
//销毁资源
this.$destroy();
},
},
在destroy
方法中,调用了系统中的$destroy
方法销毁了所有资源,这时会触发销毁阶段的钩子函数,所以这时会输出
beforeDestroy
destroyed
这时,如果你去单击“更新”按钮,就会发现什么效果也没有了,也就是无法完成元素的更新了,因为元素已经被销毁了。
下面,我们通过官方的生命周期图来再次看一下整个生命周期的流程。也是为了看一下上面所出题的C
和D
的选项是说法否正确。
beforeCreate
: Vue
实例初始化之后,以及事件初始化,以及组件的父子关系确定后执行该钩子函数,一般在开发中很少使用
created
: 在调用该方法之前,初始化会被使用到的状态,状态包括props
,methods
,data
,computed
,watch
.
而且会实现对data
中属性的监听,也就是在created
的时候数据已经和data
属性进行了绑定。(放在data
中的属性当值发生改变的时候,视图也会改变)。同时也会对传递到组件中的数据进行校验。
所以在执行created
的时候,所有的状态都初始化完成,我们也完全可以在该阶段发送异步的ajax
请求,获取数据。
但是,在created
方法中,是无法获取到对应的的$el
选项,也就是无法获取Dom
. 所以说上题中选项c
的说法是正确的。
如下代码所示:
created() {
console.log("created");
console.log("el===", this.$el);// undefined
console.log("data==", this.$data);// 可以获取数据
console.log("foo==", this.foo);//可以获取数据
},
created
方法执行完毕后,下面会判断对象中有没有el
选项。如果有,继续执行下面的流程,也就是判断是否有template
选项,如果没有el
选项,则停止整个生命周期的流程,直到执行了vm.$mount(el)
后,才会继续向下执行生命周期的流程。
下面我们测试一下:
<script>
const vm = new Vue({
// el: "#app", //去掉了el选项
data: {
foo: "fooData",
},
methods: {
update: function () {
this.foo = "hello";
},
destroy: function () {
//销毁资源
this.$destroy();
},
},
beforeCreate() {
console.log("beforCreate");
},
created() {
console.log("created");
console.log("el===", this.$el);
console.log("data==", this.$data);
console.log("foo==", this.foo);
},
beforeMount() {
console.log("beforeMount");
},
mounted() {
console.log("mounted");
},
beforeUpdate() {
console.log("beforeUpdate");
},
updated() {
console.log("updated");
},
beforeDestroy() {
console.log("beforeDestroy");
},
destroyed() {
console.log("destroyed");
},
});
</script>
在上面的代码中,我们将el
选项去掉了,运行上面的代码后,我们发现执行完created
方法后,整个流程就停止了。
现在,我们不添加el
选项,但是手动执行vm.$mount(el)
,也能够使暂停的生命周期进行下去。
如下代码所示:
<script>
const vm = new Vue({
// el: "#app",//去掉了el选项
data: {
foo: "fooData",
},
methods: {
update: function () {
this.foo = "hello";
},
destroy: function () {
//销毁资源
this.$destroy();
},
},
beforeCreate() {
console.log("beforCreate");
},
created() {
console.log("created");
console.log("el===", this.$el);
console.log("data==", this.$data);
console.log("foo==", this.foo);
},
beforeMount() {
console.log("beforeMount");
},
mounted() {
console.log("mounted");
},
beforeUpdate() {
console.log("beforeUpdate");
},
updated() {
console.log("updated");
},
beforeDestroy() {
console.log("beforeDestroy");
},
destroyed() {
console.log("destroyed");
},
});
vm.$mount("#app");//添加了$mount方法
</script>
运行上面的代码,可以看到,虽然vm
对象中没有el
参数,但是通过$mount(el)
动态添加的方式,也能够使生命周期顺利进行。
我们继续向下看,就是判断在对象中是否有template
选项。
第一:如果Vue
实例对象中有template
参数选项,则将其作为模板编译成render
函数,来完成渲染。
第二:如果没有template
参数选项,则将外部的HTML作
为模板编译(template
),也就是说,template
参数选项的优先级要比外部的HTML
高
第三:如果第一条,第二条件都不具备,则报错
下面,我们看一下添加template
的情况。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>生命周期2</title>
</head>
<body>
<script src="./vue.js"></script>
<div id="app"></div>
<script>
const vm = new Vue({
el: "#app",
template: "<p>Hello {
{message}}</p>",
data: {
message: "vue",
},
});
</script>
</body>
</html>
以上是在Vue
实例中添加template
的情况。
那么这里有一个比较有趣的问题就是,当模板同时放在template
参数选项和外部HTML
中,会出现什么情况呢?
如下代码所示:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>生命周期2</title>
</head>
<body>
<script src="./vue.js"></script>
<div id="app">
<p>你好</p>
</div>
<script>
const vm = new Vue({
el: "#app",
template: "<p>Hello {
{message}}</p>",
data: {
message: "vue",
},
});
</script>
</body>
</html>
在上面的代码中,我们添加了template
属性,同时也在外部添加了模板内容,但是最终在页面上显示的是Hello vue
而不是“你好”。就是因为template
参数的优先级比外部HTML
的优先级要高。
当然,我们在开发中,基本上都是使用外部的HTML
模板形式,因为更加的灵活。
在这里,还需要你再次思考一个问题,就是为什么先判断 el
选项,然后再判断template
选项呢?
其实通过上面的总结,我们是可以完全总结出来的。
就是因为Vue
需要通过el
的“选择器”找到对应的template
.也就是说,Vue
首先通过el
参数去查找对应的template
.如果没有找到template
参数,则到外部HTML
中查找,找到后将模板编译成render
函数(Vue
的编译实际上就是指Vue
把模板编译成render
函数的过程)。
下面,我们继续看一下生命周期的流程图。
接下来会触发beforeMount
这个钩子函数:
在执行该钩子函数的时候,虚拟DOM
已经创建完成,马上就要渲染了,在这里可以更改data
中的数据,不会触发updated
, 其实在created
中也是可以更改数据,也不会触发updated
函数
测试代码如下:
beforeMount() {
console.log("beforeMount");
console.log("beforeMount el===", this.$el);
console.log("data==", this.$data);
//this.foo = "abc"; //修改数据
console.log("foo==", this.foo);
},
通过上面的代码,我们可以获取el
中的内容,同时也可以修改数据。
但是,这里需要注意的输入的el
中的内容,{
{foo}}
还没有被真正的数据替换掉。而且对应的内容还没有挂载到页面上。
下面执行了Create VM.$el and replace "el" with it
经过这一步后,在模板中所写的{
{foo}}
会被具体的数据所替换掉。
所以下面执行mounted
的时候,可以看到真实的数据。同时整个组件内容已经挂载到页面中了,数据以及真实DOM
都已经处理好了,可以在这里操作真实DOM
了,也就是在mounted
的时候,页面已经被渲染完毕了,在这个钩子函数中,我们可以去发送ajax
请求。
mounted() {
console.log("mounted");
console.log("mounted el===", this.$el);
console.log("data==", this.$data);
console.log("foo==", this.foo);
}
所以说,最开始的问题中,D
选项:DOM渲染在mounted中就已经完成了
这句话的描述也是正确的。
下面继续看生命周期的流程,如下图所示:
当整个组件挂在完成后,有可能会进行数据的修改,当Vue
发现data
中的数据发生了变化,会触发对应组件的重新渲染,先后调用了beforeUpdate
和updated
钩子函数。
在updated
之前beoreUpdate
之后有一个非常重要的操作就是虚拟DOM
会重新构建,也就是新构建的虚拟DOM
与上一次的虚拟DOM
树利用diff
算法进行对比之后重新渲染。
而到了updated
这个方法,就表示数据已经更新完成,dom
也重新render
完成。
下面如果我们调用了vm.$destroy
方法后,就会销毁所有的资源。
首先会执行beforeDestroy
这个钩子函数,这个钩子函数在实例销毁前调用,在这一步,实例仍然可用。
在该方法中,可以做一些清理的工作,例如:清除定时器等。
但是执行到destroyed
钩子函数的时候,Vue
实例已经被销毁,所有的事件监听器会被移除,所有的子实例也会被销毁。
最后做一个简单的总结:
beforeCreate( )// 该钩子函数执行时,组件实例还未创建.
created()//组件初始化完毕,各种数据可以使用,可以使用ajax发送异步请求获取数据
beforeMounted()// 未执行渲染,更新,虚拟DOM完成,真实DOM未创建
mounted()// 初始化阶段结束,真实DOM已经创建,可以发送异步请求获取数据,也可以访问dom元素
beforeUpdate()//更新前,可用于获取更新前各种状态数据
updated()//更新后执行该钩子函数,所有的状态数据是最新的。
beforeDestroy() // 销毁前执行,可以用于一些定时器的清除。
destroyed()//组件已经销毁,事件监听器被移除,所有的子实例也会被销毁。
以上为生命周期的内容。
12、组件化应用
12.1 组件概述
在这一小节中,重点要理解的就是组件的编程思想。
组件表示页面中的部分功能(包含自己的逻辑与样式),可以组合多个组件实现完整的页面功能。
如下图所示:
问题是,如何确定页面中哪些内容划分到一个组件中呢?
但你如何确定应该将哪些部分划分到一个组件中呢?你可以将组件当作一种函数或者是对象来考虑(函数的功能是单一的),根据[单一功能原则]来判定组件的范围。也就是说,一个组件原则上只能负责一个功能。如果它需要负责更多的功能,这时候就应该考虑将它拆分成更小的组件。
当然,在上图中,我们发现’Name‘和’Price’ 表头 并没有单独的划分到一个组件中,主要考虑的是功能简单,就是展示的作用,所以没有划分到单独一个组件中。如果,该表头具有了一些比较复杂的功能,例如排序。那么这里可以单独的将表头内容划分到一个组件中。
组件有什么特点呢?
可复用、维护、可组合
可复用:每个组件都是具有独立功能的,它可以被使用在多个场景中。
可组合:一个组件可以和其它的组件一起使用或者可以直接嵌套在另一个组件内部。
可维护:每个组件仅仅包含自身的逻辑,更容易被理解和维护。
下面,看一下怎样创建组件?
12.2 组件的基本使用
组件具体的创建过程如下:
Vue.component('index', {
template: '<div>我是首页的组件</div>'
})
第一个参数指定了所创建的组件的名字,第二个参数指定了模板。
组件创建好以后,具体的使用方式如下:
<div id="app">
<index></index>
</div>
注意:1. 模板template中只能有一个根节点;2. 组件的名字,如果采用驼峰命令的话,在使用的时候,就要加上 “-”,比如组件名字叫indexA,那么在使用的时候就叫index-a。
例如:
Vue.component('componentA', {
template: "<div>创建一个新的组件</div>"
})
组件的使用
<component-a></component-a>
在Vue实例中所使用的选项,在组件中都可以使用**,但是要注意data,在组件中使用时必须是一个函数。**
下面创建一个about组件。
Vue.component('about', {
template: '<div>{
{msg}}<button @click="showMsg">单击</button></div>',
data() {
return {
msg: '大家好'
}
},
methods: {
showMsg() {
this.msg = "关于组件"
}
}
})
组件的使用如下:
<about></about>
在组件中关于data不是一个对象,而是一个函数的原因,官方文档有明确的说明
https://cn.vuejs.org/v2/guide/components.html
组件创建完整的代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>组件创建</title>
<script src="./vue.js"></script>
</head>
<body>
<div id="app">
<component-a></component-a>
<index></index>
<index></index>
<about></about>
</div>
<script>
Vue.component('componentA', {
template: "<div>创建一个新的组件</div>"
})
Vue.component('index', {
template: '<div>我是首页的组件</div>'
})
Vue.component('about', {
template: '<div>{
{msg}}<button @click="showMsg">单击</button></div>',
data() {
return {
msg: '大家好'
}
},
methods: {
showMsg() {
this.msg = "关于组件"
}
}
})
var vm = new Vue({
el: '#app',
data: {
}
})
</script>
</body>
</html>
在使用组件的时候,需要注意以下几点内容:
第一点:data
必须是一个函数
关于这一点,官方文档有比较详细清楚的说明:https://cn.vuejs.org/v2/guide/components.html
第二:组件模板中必须有一个跟元素。
第三:组件模板内容可以使用模板字符串。
Vue.component("about", {
template: `<div>
{
{msg}}
<button @click='showMsg'>单击
</button>
</div>`,
data() {
return {
msg: "大家好",
};
},
methods: {
showMsg() {
this.msg = "关于VUE组件";
},
},
});
在上面的代码中,我们在组件的模板中使用类模板字符串,这样就可以调整对应的格式,例如换行等。
第四:现在我们创建的组件是全局组件,可以在其它组件中使用。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>组件基本使用</title>
</head>
<body>
<div id="app">
<index></index>
<component-a></component-a>
<about></about>
<!-- 使用HelloWorld组件 -->
<hello-world></hello-world>
</div>
<script src="./vue.js"></script>
<script>
Vue.component("index", {
template: "<div>我是Index组件</div>",
});
// 创建了HelloWorld组件
Vue.component("HelloWorld", {
data() {
return {
msg: "Hello World",
};
},
template: "<div>{
{ msg}}</div>",
});
// 使用HelloWorld组件
Vue.component("componentA", {
template: "<div>我是一个新的组件:<HelloWorld></HelloWorld></div>",
});
Vue.component("about", {
template: `<div>
{
{msg}}
<button @click='showMsg'>单击
</button>
</div>`,
data() {
return {
msg: "大家好",
};
},
methods: {
showMsg() {
this.msg = "关于VUE组件";
},
},
});
const vm = new Vue({
el: "#app",
data: {
},
});
</script>
</body>
</html>
在上面的代码中,我们又创建了一个HelloWorld
组件,并且在componentA
组件中去使用了HelloWorld
组件,这里还需要注意的一点就是,在componentA
这个组件中使用HelloWorld
这个组件的时候,可以使用驼峰命名的方式,但是在<div id="app"></div>
这个普通的标签模板中,必须使用短横线的方式,才能使用组件。
12.3 局部组件注册
我们可以在一个组件中,再次注册另外一个组件,这样就构成了父子关系。
可以通过components 来创建对应的子组件。
组件的创建过程如下:
<script>
Vue.component('father', {
template: '<div><p>我是父组件</p><son></son></div>',
components: {
// 创建一个子组件
son: {
template: '<p>我是子组件</p>'
}
}
})
var vm = new Vue({
el: '#app',
data: {
}
})
</script>
组件的使用
<div id="app">
<father></father>
</div>
完整代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>父子组件创建</title>
<script src="./vue.js"></script>
</head>
<body>
<div id="app">
<father></father>
</div>
<script>
Vue.component('father', {
template: '<div><p>我是父组件</p><son></son></div>',
components: {
// 创建一个子组件
son: {
template: '<p>我是子组件</p>'
}
}
})
var vm = new Vue({
el: '#app',
data: {
}
})
</script>
</body>
</html>
在上面的代码中,我们是在全局的father
组件中,又创建了一个子组件son
.
那么son
这个子组件也就是一个局部的组件。也就是它只能在father
组件中使用。
当然,我们在father
中定义子组件son
的时候,直接在其内部构件模板内容,这样如果代码非常多的时候,就不是很直观。
所以这里,我们可以将son
组件,单独的进行定义,然后在father
组件中进行注册。
改造后的代码如下所示:
<!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="./vue.js"></script>
</head>
<body>
<div id="app">
<father></father>
</div>
<script>
const son = {
data() {
return {
msg: "Hello 我是子组件",
};
},
template: `<div>{
{msg}}</div>`,
};
Vue.component("father", {
template: "<div><p>我是父组件</p><son></son></div>",
components: {
// 创建一个子组件
// son: {
// template: "<p>我是子组件</p>",
// },
son: son,
},
});
var vm = new Vue({
el: "#app",
data: {},
});
</script>
</body>
</html>
在上面的代码中,我们将son
组件单独的进行了定义,这时注意写法,是一个对象的格式,在对象中包含了关于组件很重要的内容为data
函数与template
属性。
同时在father
组件中通过components
属性完成了对son
组件的注册。
我们说过son
组件是一个局部的组件,那么只能在其注册的父组件中使用。
现在,我们可以测试一下:
完整代码如下:
<!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="./vue.js"></script>
</head>
<body>
<div id="app">
<father></father>
<!-- 使用ComponentA组件 -->
<component-a></component-a>
</div>
<script>
const son = {
data() {
return {
msg: "Hello 我是子组件",
};
},
template: `<div>{
{msg}}</div>`,
};
//定义ComponentA组件
Vue.component("ComponentA", {
template: "<div><son></son></div>",
});
Vue.component("father", {
template: "<div><p>我是父组件</p><son></son></div>",
components: {
// 创建一个子组件
// son: {
// template: "<p>我是子组件</p>",
// },
son: son,
},
});
var vm = new Vue({
el: "#app",
data: {},
});
</script>
</body>
</html>
在上面的代码中,我们又创建了一个全局的组件ComponentA
,并且在该组件中使用了son
组件,注意这里没有在ComponentA
中使用components
来注册son
组件,而是直接使用。同时在<div id='app'></div>
中使用了ComponentA
组件。这时在浏览器中,打开上面的程序,会出现错误。
如果现在就想在ComponentA
组件中使用son
组件,就需要使用components
来注册。
Vue.component("ComponentA", {
template: "<div><son></son></div>",
components: {
son: son,
},
});
现在在ComponentA
组件中已经注册了son
组件,这时刷新浏览器就不会出错了。
在上面这些案例中,我们是在一个全局的组件中注册一个局部的组件,其实,我们也可以在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="./vue.js"></script>
</head>
<body>
<div id="app">
<father></father>
<component-a></component-a>
<hello-msg></hello-msg>
</div>
<script>
const son = {
data() {
return {
msg: "Hello 我是子组件",
};
},
template: `<div>{
{msg}}</div>`,
};
// 定义HelloMsg组件
const HelloMsg = {
data() {
return {
msg: "Hello World",
};
},
template: `<div>{
{msg}}</div>`,
};
Vue.component("ComponentA", {
template: "<div><son></son></div>",
components: {
son: son,
},
});
Vue.component("father", {
template: "<div><p>我是父组件</p><son></son></div>",
components: {
// 创建一个子组件
// son: {
// template: "<p>我是子组件</p>",
// },
son: son,
},
});
var vm = new Vue({
el: "#app",
data: {},
components: {
"hello-msg": HelloMsg,
},
});
</script>
</body>
</html>
在上面的代码中,我们又创建了一个组件HelloMsg
然后将HelloMsg
组件注册到了 Vue
实例中,注意:在进行注册的时候的语法格式。
左侧为组件的名称,由于这个组件创建的时候采用的是驼峰命名的方式,所以组件的名称采用短横线的方式。
右侧为组件的内容。
下面就可以在其<div id="app"></div>
中使用了。
同理,在其他的组件中是无法使用HelloMsg
组件的。
13、组件通信
13.1 父组件向子组件传值
当我们将整个页面都拆分了不同的组件以后,这样就会涉及到组件之间的数据传递问题。
常见的组件的通信可以分为三类:
第一类: 父组件向子组件传递数据
第二类: 子组件向父组件传递数据
第三类:兄弟组件的数据传递。
下面,我们先来看一下父组件向子组件传递数据的情况
第一:子组件内部通过props
接收传递过来的值。
Vue.component('menu-item',{
props:['title'] // props后面跟一个数组,数组中的内容为字符串,这个字符串可以当做属性类使用。
template:'<div>{
{title}}</div>'
})
第二: 父组件通过属性将值传递给子组件
<menu-item title="向子组件传递数据"> </menu-item>
<menu-item :title="title"></menu-item> <!--可以使用动态绑定的方式来传值-->
下面看一下具体的案例演示:
<body>
<div id="app">
<father></father>
</div>
<script>
// 创建一个父组件
Vue.component('father', {
// 2、在使用子组件的地方,通过v-bind指令来给子组件中的props赋值。
template: '<div><p>我是父组件</p><son :myName="mySonName"></son></div>',
data() {
return {
mySonName: '小强'
}
},
components: {
// 创建一个子组件
// 1.声明props,它的作用是:用来接收父组件传递过来的数据。
// props可以跟一个数组,数组里面的内容可以是字符串,这个字符串可以当属性来使用。
son: {
props: ['myName'],
template: '<p>我是子组件,我的名字叫{
{myName}}</p>'
}
}
})
var vm = new new Vue({
el: '#app',
data: {
}
})
</script>
</body>
下面我们在看一个例子,这个例子是前面我们写的关于局部组件的案例,我们在这个案例的基础上实现组件的传值。
<!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="./vue.js"></script>
</head>
<body>
<div id="app">
<father></father>
<component-a>