无题
Vue
核心技术
一、Vue
介绍
1、Vue
概念
Vue
是一个 渐进式的 JavaScript
框架
官网地址: https://cn.vuejs.org/
渐进式的理解
渐进式:逐渐增强,Vue
不强求你一次性在网站中运用学习所有的语法,可以学一点用一点
库和框架的理解
库:本质上是一些方法的集合。每次调用方法,实现一个特定的功能。
框架:是一套完整的解决方案。框架实现了大部分的功能,我们需要按照框架的规则写代码
2、Vue
是一个MVVM
的框架
什么是MVVM
呢?
M
:Model
数据模型(ajax
获取到的数据)
V
: View
视图(页面)
VM
:ViewModel
视图模型 (操作视图+模型)
在以前的开发中,我们只用到了View
视图页面和Model
数据,通过ajax
请求获取到数据以后,在通过DOM
的方式将数据渲染到View
页面中。但是这种开发方式有一个问题,需要我们手动操作DOM
,这样导致了开发效率比较低。
为了能够提高开发效率是否可以将DOM
的操作做成自动的方式呢?也就是说,当数据变化了自动的渲染到页面中,不需要开发人员在操作DOM
.
为了满足这个要求,出现了VM(ViewModel)
,它可以操作View(视图)
也可以操作Model(数据)
。当数据(Model
)变化的时候,ViewModel
能够监听到这种变化,并及时通知View
视图做出修改。同样的,当页面有事件触发的时候,ViewModel
也能够监听到事件,并通知数据(Model
)进行响应。所以ViewModel
就相当于一个观察者,监控着双方的动作,并及时通知对方进行相应的操作。
总结:
之前的开发方式是原生DOM
驱动,无论修改页面中的什么内容,先找到对象,再去操作DOM
现在的开发方式是,**Vue 数据驱动
**,要想更新页面,直接操作数据就可以了,数据变化了,视图会自动更新。
3、Vue
组件化思想
我们知道一个网页会包含三部分,分别是HTML
,CSS
,JS
. 在开发一个页面的时候,如下网站
以上是网易网站中的国内新闻页面,
我们需要将html
代码写到一个html
网页文件中,然后把css
代码写到一个css
样式文件中,把javascript
代码写到一个js
文件中。
然后在html
网页文件中去引入css
文件和js
文件。
这样会带来一个问题,不利于代码的维护,假如html
文件中的代码3000行,你要找其中某个版块的结构,这样查找起来比较麻烦。
为了解决这个问题,我们是否可以将上面的网页进行拆分,例如将头部区域的html,css,js
作为一个整体,单独的去维护,这样代码量相对来说比较少,查找方便,容易维护。
这样也带来了另外一个好处,如果要开发另外一个新的页面,如下所示:
以上是网易国际页面
依据:功能单一 函数
我们发现上面的页面与前面页面头部是一样的,所以可以共用,不用单独的再去开发头 部版块了。
这样,我们可以将一个大的页面拆分成不同的小的模块(每个模块都是单独的一个文件),每个模块中都包含了html,css,js
。
而这些小的模块,在Vue
中称作组件,如下图所示:
一个大的页面就是有不同的组件组合而成,而这就是Vue
组件化的思想。
所谓组件化,指的就是将一个完整的页面拆分成多个组件,每个组件都会包含html,css,js
.
综上所述组件化的好处:
第一:容易维护
第二:便于复用。
二、脚手架
1、脚手架基本介绍
关于Vue
的开发方式有两种
第一:传统开发模式,在html
文件中引入vue
文件
第二:工程化开发方式:在webpack
环境中开发Vue
(推荐),后面的开发采用该方式。
webpack
的作用:
javascript
应用程序的 静态模块打包器 (module bundler)
其中功能:
less/sass -> css
ES6/7/8 -> ES5 处理js兼容
- 支持
js
模块化 export import - 处理
css
兼容性 html/css/js
-> 压缩合并
问题:自己配置Webpack
非常痛苦,有没有一套搭建好的,拿来即用的环境呢?
这就是脚手架的工作。
脚手架,在日常生活中脚手架非常常见,主要作用就是为了保证各施工过程顺利进行而搭设的工作平台,
而Vue
脚手架可以帮助我们快速的创建一个Vue
项目的基础架子。Vue
脚手架叫做@vue/cli
,它是Vue
官方所提供的一个全局命令工具。
Vue
脚手架有什么好处呢?
第一:开箱即用
第二:零配置(不用你配置webpack
)
第三:内置了babel
等工具。
2、Vue
脚手架的基本使用
要想使用@vue/cli
脚手架搭建Vue
项目,需要先进行安装。
2.1 安装方式采用全局安装
命令如下:
1 | npm i @vue/cli -g 或 yarn global add @vue/cli |
当然,首先必须在电脑中安装node.js
环境
查看node
和 npm
的版本
1 | node -v #查看node版本 |
**npm
淘宝镜像**
npm
是非常重要的npm
管理工具,由于npm
的服务器位于国外, 所以一般建议 将 npm
设置成国内的淘宝镜像
设置淘宝镜像
1 | $ npm config set registry https://registry.npm.taobao.org/ #设置淘宝镜像地址 |
查看脚手架版本:
1 | vue --version |
2.2、使用脚手架搭建Vue3
开发环境
在指定的目录下面执行
1 | vue create 项目名称(项目名称自己定义,并且不能出现中文,以及大写字母) |
使用以上命令创建项目的时候,有可能会出现了如下错误
1 | vue : 无法加载文件 C:\XXX\AppData\Roaming\npm\vue.ps1,因为在此系统上禁止运行脚本。 |
参考解决方案:
1 | https://blog.csdn.net/ying456baby/article/details/126379392 |
第一:以管理身份启动命令窗口
第二:在终端窗口输入 **set-ExecutionPolicy RemoteSigned**
输入命令查看 **get-ExecutionPolicy**
为 “**RemoteSigned**”
项目创建的步骤如下
第一步:打开命令行窗口(以管理员身份)
第二步:在打开的命令行窗口中输入vue create 项目名称
第三步: 选择Vue3
项目(关于自定义的方式,我们后面做项目的时候,在进行讲解)
2.3 项目创建好以后 cd
进入目录,启动项目, 打包项
1 | 启动项目:yarn serve 或 npm run serve |
3、如何覆盖脚手架下的webpack
配置`
我们说 @vue/cli
脚手架集成了 webpack,
但是我们怎么没有看到 webpack.config.js
呢?
注意:我们在项目无法找到webpack.config.js
文件,因为vue
把它隐藏了
如果需要覆盖webpack
的配置,可以修改vue.config.js
文件,覆盖webpack
配置
注意:修改了配置,需要重新启动项目。
4、脚手架目录分析
问题: 脚手架所创建的Vue
项目里各个文件及代码都有什么作用呢?
下面,我们就来了解一下项目中文件夹和文件的含义。
src
目录是我们后面编程中经常使用到的目录。这里我们可以暂时先将该目录中使用不到的文件删除掉。
public/index.html
不用动,提供一个最基础的页面src/main.js
不用动, 渲染了App.vue
组件src/App.vue
默认有很多的内容,可以全部删除assets
文件夹 和 components 直接删除
通过本小节的学习,大家需要对脚手架里主要文件的左右有一个基本的了解
这里还需要注意一个小的问题:在使用vscode
编写vue
代码之前,最好在vscode
中安装相应的插件,方便有代码提示
5、单文件组件
.vue
文件是什么文件呢? 以前怎么没见过呢
一个单文件组件由三部分组成:
template
:表示结构 (有且只能一个根元素)
script
:表示js
逻辑
style
: 表示样式
三、Vue
插值表达式
1、Vue
如何提供数据
问题: vue
是数据驱动的,应该如何提供数据,将来控制视图呢?
第一:通过 data
属性可以提供数据, data
属性必须是一个函数
第二:这个函数需要返回一个对象,这个对象就代表vue
提供的数据
使用插值表达式,可以在模板中渲染数据:
2、插值表达式的语法
插值表达式的作用就是使用data
中的数据来渲染视图(模板),它的语法如下所示:
1 | 通过上图可以看到,插值表达式也是支持三元运算符的。 |
具体代码演示如下图所示:
1 | export default{ |
在App.vue
组件中提供了相应的数据,下面看一下模板中是怎样使用插值表达式将数据渲染到模板中的。
1 | <template> |
四、指令
1、什么是指令
Vue
指令:特殊的 html
标签属性, 特点:v-
开头
每个 v- 开头的指令, 都有着自己独立的功能, 将来vue
解析时, 会根据不同的指令提供不同的功能
1 | <div v-text="username"></div> |
2、v-bind
指令
插值表达式不能用在html
的属性上,想要动态的设置html
元素属性,需要使用v-bind
指令
所以说,v-bind
指令的作用就是用来动态设置html
标签属性。
它的语法是:·v-bind:属性名 = "值"
我们一般都是使用简写的形式::属性名 = "值"
1 | <div v-bind:title="obj.desc">hello</div> |
下面再来看一下简写的形式:
1 | <div :title="obj.desc">hello</div> |
1 | <a :href="link">百度</a> |
1 | data(){ |
注意:在给标签添加属性的时候,如果是采用如下的写法:
1 | <div title='msg'> </div> |
表示的是给div
这个标签设置了title
属性,该属性的值是字符串msg
.
但是如果采用如下的写法:
1 | <div :title='msg'></div> |
表示的是给div
这个标签设置了title
属性,该属性的值是来自msg
这个变量,而该变量需要在data
中进行定义。
3、v-on
指令
这里有一个问题,需要我们思考一下:在Vue
中如何给按钮绑定点击事件呢?
这里需要使用到v-on
指令,该指令的作用就是注册事件。
该指令有三种使用方式,如下所示:
第一:v-on
:事件名=“要执行的少量代码”
第二:v-on:
事件名=”methods中的函数名”
第三:v-on
:事件名=“methods中的函数名(实参)”
下面我们先来看第一种方式的使用情况,
1 | <button v-on:click="obj.age++">搬砖</button> |
通过上图展示的代码,我们可以看到,如果计算比较简单,只需要少量代码就可以完成,这样就没有必要在将代码封装到方法里了,只需要写到模板中就可以了。
下面我们再来看第二种使用方式
1 | <button v-on:click="obj.age++">搬砖</button> |
1 | data(){ |
在模板中,给v-on:click
指定的是addAge
这个方法名。问题是addAge
这个方法应该定义在什么地方呢?
这里需要注意的就是方法需要定义在methods
中,methods
是一个属性,该属性的值是一个对象,在对象中定义了方法。
同时data
这个选项和methods
这个选项之间需要使用英文的逗号进行分隔,如果漏掉英文的逗号,程序会出现错误。
在addAge
这个方法中,我们需要对定义在data
中的数据进行计算,这里就涉及到一个问题,怎样获取data
中定义的数据呢?
在methods
选项中定义的所有方法,如果要访问或者是修改data
中的数据,需要使用到this
这个关键字,this
表示的是当前vue
实例。
通过this
关键字就可以获取到data
中定义的数据。
但是,这里需要大家注意的一点是,整个过程中我们并没有操作DOM
,而只是操作数据,当数据发生了改变,视图也会发生相应的变化。
下面我们再来看第三种使用方式
1 | <button v-on:click="addAge(2)">计算年龄</button> |
1 | methods:{ |
注意:如果在methods
中定义的不同方法之间也要使用英文的逗号进行分隔。
当然,需要传递多少个参数,根据实际情况来确定。
每次使用v-on
的时候写起来比较麻烦,我们可以直接将其简写为@事件名
的形式, 如下图所示:
1 | <button @click="addAge(2)">计算年龄</button> |
4、如何获取事件对象
默认a
标签点击会跳走, 希望阻止默认的跳转, 应该如何操作呢?如何阻止默认行为呢?
语法:e.preventDefault()
vue
中如何获取事件对象呢?
关于Vue
中获取事件对象,有两种情况
第一:如果没有传参,通过形参接收e
第二:如果传参了,通过$event
指定事件对象e
.
1 | <a :href="link" @click="fn">百度</a> |
1 | methods:{ |
下面看一下第二种情况
1 | <a :href="link" @click="fn(100,$event)">百度</a> |
1 | fn(num,e){ |
当单击去百度
这链接的时候,会触发单击事件click
,从而调用fn
这个方法,在调用该方法的时候,没有传递任何的参数,所以在fn
这个方法中通过形参e
来获取事件对象。
而当单击去百度2
这个链接的时候,也会触发单击事件click
,调用fn2
这个方法,这时候需要通过$event
指定事件对象e
.
5、事件修饰符
e.preventDefault()
单词很长不好写吧?
有没有一种更简单的方式实现呢?
这里就需要使用到事件修饰符
事件修饰符:vue
提供事件修饰符,可以快速阻止默认行为或阻止冒泡
.prevent
阻止默认行为,.stop
阻止冒泡
使用方式:@事件名.prevent
,@事件名.stop
1 | <div @click="fatherFn"> |
1 | fn1(){ |
在第一个超链接和第二个超链接中,即指定了.prevent
又指定了.stop
.表示的含义是:当单击两个链接的时候,分别会执行fn1
和fn2
两个函数,但是由于添加了.prevent
事件修饰符,所以不会跳转到京东与淘宝
。同时由于添加了.stop
这个事件修饰符,这里可以阻止冒泡,所以对应的两个a
元素的父元素div
中的单击事件不会触发,对应的fatherFn
这个方法不会被调用。
注意:以上事件修饰符的使用方式,采用的是链式的写法,但是.prevent
和.stop
两个修饰符没有顺序的限制。
6、按键修饰符
我想判断用户是否按下回车了怎么做?
在监听键盘事件时,我们经常需要判断详细的按键。可用按键修饰符。
需求: 用户输入内容, 回车时, 打印输入的内容
@keyup.enter
监听回车键
@keyup.esc
监听返回键
下面看一下具体的代码实现
1 | <hr/> |
1 | handleKeyUp(){ |
注意:在上图所展示的代码中,我们仅仅给input
标签指定了@keyup
事件。这样导致的结果是:当在文本框中输入任何内容的时候,都会调用handleKeyup
这个方法,原因是@keyup
监听的是键盘的弹起,也就是说键盘上的任意按键的弹起都会被监听到。
而这里,我们要求的是,用户在文本框中输入了内容后,按下回车键的时候,才会去调用handleKeyup
这个方法打印相关的内容。
修改后的代码如下所示:
1 | handleKeyUp(e){ |
在上面的代码中,我们是通过事件对象e
来获取对应的key
属性的值,判断是否是Enter
。如果是,表示用户按下了回车键,这时候才会打印相关的内容。
这种处理方式可以满足我们的需求,但是写法上比较繁琐。
下面使用按键修饰符:
1 | <hr/> |
1 | handleKeyUp(){ |
在这里我们给input
文本框添加了@keyup.enter
,表示只有当用户按下了回车键后,才会执行handleKeyup
这个方法打印相关的内容。当然在handleKeyup
方法中不用在通过事件对象做按键的判断了。
这种写法更加的简洁。
如果想获取到用户在文本框中输入的内容,还是需要通过事件对象来完成
1 | handleKeyUp(e){ |
Vue
内置的按键修饰符列表如下所示:
通过上图我们可以看到,在Vue
中只是提供了常用了的按键修饰符,如果你想对其它的一些按键做判断,只能采用事件对象的方式,来获取对应的键盘码或者键盘标识进行判断。
7、v-show
和v-if
问题:在Vue
中如何控制盒子的显示与隐藏呢?
这里可以通过v-show
或者v-if
指令来实现。
下面我们先来看一下v-show
指令。
v-show
指令的语法:
1 | v-show="布尔值" (true显示, false隐藏) |
v-show
指令的取值是布尔值,如果是true
表示显示盒子,false
表示隐藏盒子。
它的实现原理:实质是在控制元素的 css
样式, 通过display:none
隐藏元素
再来看一下v-if
指令
v-if
指令的语法如下:
1 | v-if="布尔值" (true显示, false隐藏) |
v-if
指令的取值也是布尔值,true
表示元素的显示,false
表示元素的隐藏。
它的实现原理:实质是在动态的创建 或者 删除元素节点
下面看一下具体的代码实现
1 | <script> |
1 | <template> |
这里在data
函数中定义了具体的数据isShow
,默认值是true
在模板中两个h3
元素分别通过v-show
和v-if
两个指令来控制显示与隐藏,两个指令的值就是isShow
当单击button
按钮的时候,触发点击事件,将改变isShow
数据的值。
返回到浏览器中进行测试的时候,发现当不断单击按钮
时候,两个h3
标签会不断出现显示与隐藏的效果,这里大家可以在浏览器中审查元素,就会发现两个指令在显示与隐藏元素实现原理上的区别。
问题:两个指令的应用场景是什么?
第一:如果是频繁的切换显示隐藏,用v-show
.
因为v-show,
只是控制css
样式,而v-if
, 频繁切换会大量的创建和删除元素, 消耗性能
第二:如果是不用频繁切换,而是出现了 要么显示, 要么隐藏的情况, 适合于用 v-if
v-if
是惰性的, 如果初始值为 false
, 那么这些元素就直接不创建了, 节省一些初始渲染开销
8、v-else
和 v-else-if
关于v-else
和v-else-if
两个指令,与js
中的if..else/else if
执行流程是一样的、
例如上图展示的代码中,先判断flag
是否为true
,如果为true
展示的内容就是尊敬的超级vip,里面请
,而后面的内容不会展示,如果flag
为false
,则展示的内容就是你谁呀,请先登录
下面紧跟着判断age
的取值范围,根据age
不同的值,展示不同的内容。当前定义的age
默认值是12
,所以打开浏览器后,在页面上默认展示的内容就是:11岁-20岁 蹦迪
最后需要注意的一点就是v-if,v-else,v-else-if
指令之间需要连接写,如下的写法是错误的。
在v-if
与v-else-if
之间添加了div
标签,这时候就出错了。
9、v-model
v-model
指令是给表单元素使用,可以实现双向数据绑定。
什么是双向数据绑定?
(1) 当数据发生了变化,视图会跟着变化。
(2) 当视图变了,数据也会跟着变化。
语法: v-model ='变量值'
代码如下所示:
1 | data(){ |
1 | <hr/> |
我们看到,在上图的代码中,我们在data
中定义l了msg
,初始值是abc
字符串。
然后在模板中两个文本框通过v-model
都绑定了msg
,所以在页面中展示的两个文本框中的值是abc
字符串。
当我们修改视图中一个文本框中的值时,另外一个文本框中的值也发生了变化,说明:修改视图,数据会发生改变。
当单击了修改msg
按钮的时候,修改了msg
数据,视图中文本框的值也发生了变化。
这就是我们所说的双向数据绑定。
当然,这里让两个文本框绑定了同一个数据,但是在实际的应用中,每个文本框都会单独的绑定一个数据。
1 | <hr/> |
1 | data(){ |
在模板中添加了登录
按钮,并且注册了单击事件,事件触发以后会调用login
方法。在该方法中可以通过this.username
和this.password
来获取用户输入的姓名和密码。这就是视图发生了变化,数据也会进行改变。这里大家可以与传统的js
编程做一下对比,这里不需要操作DOM
,直接操作数据,更加的方便。
综上所述,v-model
可以快速的收集和设置表单中的数据。
10、v-model
处理其他表单元素
在这一小节中,我们看一下v-model
处理其他的表单元素。
先来看一下对下拉框的处理
1 | <hr/> |
1 | data(){ |
这里定义了cityId
数据,初始值是103
,并且通过v-model
与select
标签进行了绑定。
这时候,打开浏览器查看,就会发现下拉框中默认选中的就是广州
这一项,因为它的option
选项中value
属性的值就是103
.
并且在下拉框旁边会展示出cityId
的值。
下面再来看一下复选框的使用
1 | <hr/> |
1 | data(){ |
这里定义了isSingle
数据,初始值是false
,并且通过v-model
与复选框进行了绑定,看一下效果,这时候,我们可以看到复选框是不会被选中的。
当选中了复选框以后,isSingle
的值就会变成true
,也就表示复选框被选中了。
关于其它的表单元素,在后面使用到的时候在进行讲解。
11、v-model
修饰符
语法: v-model.修饰符
=”数据变量”
(1) .number
转数字,以parseFloat
t转成数字类型
(2) .trim 去除首尾空白字符
(3) .lazy
在change
时触发而非input
时
下面先来看一下.number
的使用,
1 | <hr/> |
1 | data(){ |
在上面的代码中,定义了age
数据,默认值是18,并且与年龄
文本框进行了绑定,这时候文本框中会展示18.
这里如果我们在文本框中输入的字符串也是没有问题的。
1 | 年龄:<input type="text" v-model.number="age"/>{{ age }} |
这时候在输入字符串,视图页面中没有变化(注意:在数字后面输入字符串)
注意:如果用户在年纪
文本框中输入了一个字符串abc
,这时候是无法进行转换的,这时候age
中的值就是abc
字符串,当然,后面我们也会对文本框进行校验。
下面再来看一下.trim
修饰符的使用
1 | <hr/> |
1 | data(){ |
这时候在文本框中会默认显示hello
这个字符串,当然,这里我们可以在该字符串前面输入空格
1 | <hr/> |
当添加了.trim这个修饰符以后,在
hello`这个字符串前输入空格的时候,会自动的清除掉空格。
最后再来看一下.
lazy修饰符的使用
1 | <hr/> |
1 | data(){ |
默认情况下描述
这个文本框只是通过v-model
与desc
进行了绑定,这时候在文本框中输入内容desc
中的数据就会发生变化
出现这个情况的原因是,当给文本框
添加了v-model
以后,Vue
会给文本框添加一个input
事件,这样用户只要在文本框中输入内容,input
事件就会触发。也就是说input
事件是实时触发,只要修改文本框内容,就会触发。
还有一个事件是change
事件,当在文本框中输入完内容并且失去焦点后或者是按下回车键的时候,触发change
事件。要想实现该效果就需要添加.lazy
修饰符。
1 | <hr/> |
现在在描述
文本框中输入了新的数据,但是文本框还没有失去焦点,这时候desc
中的数据并没有发生改变。
当输入完内容按下回车键或者是文本框失去焦点后,desc
中的内容就会发生改变了。
最后总结一下:
(1).number
– 转成数值类型赋予给Vue
数据变量
(2) .trim
– 去除左右两边空格后把值赋予给Vue
数据变量
(3).lazy
– 等表单失去焦点, 才把值赋予给Vue
数据变量
12、v-text
和v-html
v-text
和v-html
的作用是:更新元素的innerText/innerHTML
1 | <hr/> |
1 | data(){ |
通过执行效果,我们可以看到v-text
是不解析标签的,而v-html
会解析标签。所以说,v-text
就是innerText
,而v-html
就是innerHTML
关于v-text
指令我们不常用,一般都是使用插值表达式。
而当系统后台返回一些标签节点的时候,我们经常会使用v-html
指令进行动态解析渲染。但是这里需要注意的是,永远不要直接将用户输入的内容直接作为v-html
指令的值,容易造成XSS
攻击(跨站脚本攻击)。因为用户的输入中可能包含js
脚本,v-html
会解析js
脚本,从而导致js
被执行,从而产生攻击漏洞。
13、v-for
指令
v-for
的作用:可以遍历 数组 或者 对象,用于渲染结构
下面先来看一下关于数组的遍历
遍历数组的语法,如下:
1 | v-for="item in 数组名" |
具体代码实现如下所示:
1 | <hr/> |
1 | data(){ |
在上面的代码中,定义了数据arrList
,它是一个数组。在视图中可以通过v-for
来遍历arrList
这个数组,而item
就是数组中的每一项。所以将item
中存储的数据展示到了li
标签中。注意这里需要指定key
属性,它的值暂时为item
.key
属性在这里起到唯一标识的作用
下面我们再来看一下v-for
遍历数组的第二种使用方式
1 | v-for="(item, index) in 数组名 |
1 | <ul> |
在上面的代码中,指定了index
,并且在展示完了item
中的数据后又展示了index
中的数据
这里不仅展示了数组中的每一项,同时将其下标也给展示出来了。
注意:item
与index
的名称可以根据自己的情况随意命名,但是两者的顺序不能更换。第一个表示的就是数组中的每一项,第二个表示的就是数组中每一项的下标。
下面看一下针对对象的遍历
遍历对象的语法
1 | v-for = "(value, key) in 对象名" |
1 | data(){ |
1 | <ul> |
在上面的代码中定义了girlFriend
对象,在视图中对该对象进行遍历。其中的value
中存储的是属性值,而key
中存储的是属性名。
同时也需要给v-for
添加key
这个属性作为唯一标识
最后再来看一下遍历数字,对应的语法如下
1 | v-for = "item in 数字 |
1 | <div v-for="item in 30" :key="item">{{ item }}</div> |
这里通过v-for
遍历数字,item
中存储的就是1--20
的数字。
以上就是v-for
指令的基本使用。
14、虚拟Dom
html
渲染出来的 真实dom
树,是个树形结构(复杂)。每个标签,都只是树的某个节点
每个标签,虽然只是树形结构的一个小节点,但属性也非常多。=> 遍历真实dom
找差异,非常费时!
真实DOM属性过多, 有很多无用的属性 ,无需遍历对比。
如何优化呢?对比属性少的虚拟dom
虚拟dom
:本质就是 保存节点信息, 描述真实dom
的 JS
对象
虚拟dom(一个js对象)
: 可以用最少的属性结构,描述真实的dom
1 |
|
如上demo
排序,虽然在使用jquery
时代这种方式是可行的,我们点击按钮,它就可以从小到大的排序,但是它比较暴力,它会将之前的dom
全部删除,然后重新渲染新的dom
节点,我们知道,操作DOM
会影响页面的性能,并且有时候数据根本就没有发生改变,我们希望未更改的数据不需要重新渲染操作。
因此虚拟DOM
的思想就出来了,虚拟DOM
的思想是先控制数据再到视图,但是数据状态是通过diff
比对,它会比对新旧虚拟DOM
节点,然后找出两者之前的不同,然后再把不同的节点再发生渲染操作。
所以可以概括的说,虚拟DOM
,就是为了最小化找出差异这一步的性能损耗而出现的.
下面我们看一个案例,该案例的功能比较简单,单击按钮后,更新div
中的内容。
1 | <div>aaa</div> |
以上代码非常简单,而且是使用DOM
操作的方式来实现的。
如果上面的案例,我们使用虚拟DOM
来实现,应该怎样处理呢?首先,我们要创建一个虚拟DOM
的对象,
虚拟DOM
对象就是一个普通的JS
对象。当单击按钮的时候,需要对比两次状态的差异。所以说,仅仅是该案例,
我们使用虚拟DOM
的方式来实现,要比使用纯DOM
的方式来实现,性能要低。
所以说,并不是所有的情况下使用虚拟DOM
都会提升性能的。只有在视图比较复杂的情况下使用虚拟DOM
才会提升渲染的性能。
虚拟DOM
除了渲染DOM
以外,还可以实现渲染到其它的平台,例如可以实现服务端渲染(ssr
),原生应用(React Native
),小程序(uni-app
等)。以上列举的案例中,内部都使用了虚拟DOM
.
15、Diff
算法
明确:通过对比 新旧虚拟dom
,提高了对比性能。 但是就算是虚拟dom
,和真实dom
一样,也是树形结构 内部又是如何对比的呢?
内存中创建虚拟dom
,快速比较变化, 给真实DOM
打补丁(更新).
1 | diff算法可以看作是一种对比算法,对比的对象是新旧虚拟Dom。顾名思义,diff算法可以找到新旧虚拟Dom之间的差异,但diff算法中其实并不是只有对比虚拟Dom,还有根据对比后的结果更新真实Dom。 |
在这有一个问题,虚拟DOM在什么时候会进行比对?
当数据发生了变化的时候,就要进行虚拟DOM的比对了。
什么时候会发生数据的改变呢?当data
中的数据发生了变化。这时就需要使用Diff
算法进行比对了。
策略1:先同层级根元素比较。 => 如果根元素变化,那么不考虑复用,整个dom树
删除重建
策略1:先同层级根元素比较。 => 如果根元素不变,对比出属性的变化更新,并考虑往下递归复用。
策略2:对比同级兄弟元素时,默认按照下标进行对比复用
对比同级兄弟元素时,如果指定了 key,就会按照相同 key 的元素来进行对比
小结:
1 | 1. 同层级根元素先比较 |
思考:
1 | 同层级兄弟元素比较新旧变化,默认按下标比较, |
无key的情况:默认diff
更新算法,是同级兄弟,按照 下标 对比新旧dom
的差异
以上没有给元素指定key
,只能通过下标来对比新旧元素,性能相对来讲比较低
有key的情况:根据diff
更新算法,同级兄弟元素,在设置了key
后,会让相同key
的元素进行对比
key的要求:必须是字符串 或者 数字,且要保证唯一性!(标准的key需要指定成 id)
通过上图,我们可以看到,老大,老二,老三,在新的虚拟DOM
结构中key
都是一样的,没有发生变化,这时候,新的DOM
结构中新增了一个元素
列表循环加:key=”唯一标识”,可以标识元素的唯一性,可以更好地区别各个元素。
key的作用:提高虚拟DOM的对比复用性能
小节:
1 | 1. 设置 和 不设置 key 有什么区别? |
但是有一点要注意,就是这个key值尽量不能为数组的索引的值,因为,如果key的值为index的话,就无法保证原始的虚拟DOM上的key值和新的虚拟DOM上的key值相同了,因为数组的index的值是随着数组里元素的添加或者是删除,会发生变化的。
所以,尽量不要使用index作为key值。
key
实际工作中应用的案例:
1 | <template> |
16、样式控制
16.1 控制类名
控制样式,要么操作类,要么操作行内样式, 在vue
中,应该如何操作 class 类呢?如何操作style
行内样式呢
1 | 语法 :class="对象/数组" |
1 | <template> |
下面再来看另外一种比较常用的使用方式:
根据属性的值是true
还是false
来决定是否添加某个类样式
1 | data(){ |
这里定义了isOrage
属性,根据该属性的取值来决定是否采用某个类样式
1 | <div class="box" :class="{orange:isOrage}" ></div> |
如果isOrage
这个属性的值是true
,就给div
添加orange
这个类样式,如果为false
就不添加orange
这个类样式
下面再来看一下数组的使用
1 | data(){ |
1 | <!-- [类名1, 类名2, 类名3, ...] 数组里面有的项,就是要添加的类 (适合批量添加多个类)---> |
16.2 控制行内样式
用 v-bind
动态设置标签的style
行内样式
语法:
1 | :style="对象/数组" |
1 | <div :style="{ background: 'red', width: '200px', height: '100px'}"></div> |
下面看一下另外一种用法:
1 | data(){ |
1 | <div :style="styleObj"></div> |
通过style
动态绑定了styleObj
对象
1 | <div :style="[styleObj, obj2]"></div> |
通过style
动态绑定了一个数组
五、Composition API
1、Composition API
设计动机
Vue2.x
在设计中小型项目的时候,使用非常方便,开发效率也高。但是在开发一些大型项目的时候也会带来一定的限制,
在Vue2.x
中使用的API
是Options API
,该类型的API
包含一个描述组件选项(data
,methods
,props
等)的对象,在使用Options API
开发复杂的组件的时候,同一个功能逻辑的代码被拆分到不同的选项中,这样在代码量比较多的情况下就会导致不停的拖动滚动条才能把代码全部看清,非常的不方便。
1 | <template> |
可以将业务写到setup
入口函数中,如果业务比较复杂,可以单独的在封装函数,然后在setup
中进行调用。
2、reactive/toRefs/ref
2.1 reactive
函数基本使用
在上面的案例中定义的msg
是一个普通的数据,并不是响应式的。而放在data
函数中的数据都是响应式的。
而在Vue3
中怎样创建响应式数据呢?
Compositiont API
中的三个函数reactive/toRefs/ref
创建响应式数据
reactive
函数,可以将对象转换成响应式。
1 | <template> |
单击计算年龄
按钮 的时候,调用addAge
函数,在该函数中完成了age
属性值的更新,同时视图中数据也发生了相应的变化。
2.2 ref
函数基本使用
ref
函数的作用就是把普通数据转换成响应式数据。与reactive
不同的是,reactive
是把一个对象转换成响应式数据。
ref
可以把一个基本类型的数据包装成响应式对象。
KilgourNote:
插值表达式会自动将ref过的对象取value
下面,我们使用ref
函数实现一个自增的案例。
1 | setup(){ |
通过ref
函数创建一个num
,初始值是0
1 | // 完成num值累加操作 |
创建一个addNum
函数,接收传递过来的数值,然后完成累加操作。
注意:获取num
的值需要通过value
属性。
最后返回num
和addNum
函数,这样在模版中才能够使用。
1 | <p>num:{{ num }}</p> |
注意: 需要从vue
中导入ref
函数。
2.3 toRefs
函数基本使用
1 | <p>年龄:{{ age }}</p> |
这里如果想在模版中展示obj
对象中age
属性,可以简写成如上形式
1 | return { |
这里就需要将obj
对象进行解构。
但是,当单击计算年龄
按钮的时候,模版中的年龄
数据没有变化,说明解构完以后,数据不是响应式的。
为了解决这个问题,需要使用到toRefs
函数。
1 | import { reactive, ref,toRefs } from 'vue'; |
导入toRefs
函数
1 | return { |
通过toRefs
函数包裹了obj
对象。
这时候解构出的数据就是响应式的。
3、学生列表案例
3.1 基本结构
创建一个学生列表.vue
组件。
同时修改一下main.js
入口文件中的代码
1 | import { createApp } from 'vue' |
上面导入的是学生列表组件
该组件中的基本结构代码,如下所示:
1 | <template> |
上面使用的是less
构建的样式,为了能够进行解析,需要安装如下包
1 | npm install less-loader less |
3.2 展示数据
1 | import { reactive,toRefs } from 'vue'; |
构建数据
模版中的代码
1 | <thead> |
进行日期格式的处理。(https://momentjs.com/
)
安装日期处理的包moment
1 | npm install moment |
定义日期处理的函数,并且返回
1 | import moment from 'moment' |
修改模版中的代码,调用format
函数
1 | <td> |
最后,将小于60分的成绩,添加样式
1 | <td :class="{red:item.score < 60 }"> |
判断的条件成立,将使用red
样式
3.3 添加数据
1 | const list=reactive({ |
定义响应数据subject,score
,然后与视图中的文本框进行绑定。
1 | <input type="text" placeholder="请输入科目" v-model.trim="subject"/> |
给按钮注册单击事件
1 | <button class="submit" @click="add">添加</button> |
1 | // 添加数据 |
完成数据的添加
3.4 数据删除
1 | <td> |
给删除的链接,注册单击事件,去掉默认行为。
1 | // 删除数据 |
六、计算属性
1、计算属性说明
计算属性: 一个特殊属性, 值依赖于另外一些数据变化动态计算出来
1 | 注意: |
计算属性特点:所使用的变量改变, 重新计算结果返回
参考文档:https://cn.vuejs.org/guide/essentials/computed.html
(根据文档进行说明)
KilgourNote:
只要template中的属性发生变化,则全部的template代码都得执行一遍,因此,使用累加、计算总和这样的函数就会被重新执行一遍。
这大大增加了内存的负担,计算属性也就应运而生了。
2、通过计算属性计算总分与平均分
计算总分
1 | import { computed, reactive,toRefs } from 'vue'; |
导入computed
1 | // 通过计算属性完成总分与平均分的计算 |
模版中的代码
1 | <span>总分:{{ total }}</span> |
计算平均分
1 | // 计算平均分 |
模版中的代码:
1 | <span style="margin-left:50px">平均分:{{avg}}</span> |
3、计算属性缓存
若我们将同样的函数定义为一个方法而不是计算属性,两种方式在结果上确实是完全相同的,不同之处在于计算属性值会基于其响应式依赖被缓存。一个计算属性仅会在其响应式依赖更新时才重新计算。
例如上面的代码中,只要计算属性所依赖的list.stuList
中的数据没有变化,也就是不会发生改变,不管访问多少次,都会立即返回之前的计算结果,而不是重复执行计算。实现了缓存的效果。
但是,如果定义成普通的方法来实现,就会每次访问的时候,都会调用所定义的方法重新计算,不管list.stuList
中的数据是否发生变化。
所以计算属性提升了性能。
总结:
1 | 计算属性特点? |
4、计算属性完整写法
计算属性默认是只读的。当你尝试修改一个计算属性时,你会收到一个运行时警告。只在某些特殊场景中你可能才需要用到“可写”的属性,你可以通过同时提供 getter
和 setter
来创建:
创建一个计算属性.vue
组件,进行演示。
1 | <template> |
上面代码中,定义了obj
对象。并且将该对象中的属性与视图中的文本框进行了绑定。
这里创建了一个计算属性fullName
,当在视图中展示fullName
的时候,会执行get
函数。
问题:什么时候执行set
函数呢?
当更新计算属性的时候,才会执行。
1 | <span>{{ fullName }}</span> |
在模版中添加了更改姓名
按钮
1 | // 更改姓名 |
在changeName
方法中,完成了对fullName
值的修改。
这时候会执行set
方法。
下面要做的就是完善set
方法,给obj
对象中的name1和name2
属性进行赋值,
1 | const fullName = computed({ |
当set
方法执行完毕以后,文本框中的内容也发生了变化。
5、实现全选与反选
展示基本的数据
1 | <template> |
模版中添加一个全选的复选框
1 | <div> |
这里给复选框双向绑定了isAll
这个计算属性。当复选框的状态发生变化的时候,会执行isAll
这个计算属性的set
方法(修改计算属性的值)。
例如:最开始的时候,全选
复选框没有选中,当单击该复选框,将其选中,会执行set
方法,在该方法中的value
参数值就是true
。
这样数据中的flag
属性都是true
,所有的小复选框都被选中了。
当然,第一次打开页面的时候,也会执行isAll
这个计算属性的get
方法,这时候,我们发现,有的小复选框没有选中,这样every
返回的就是false
,从而全选
复选框也不会被选中
1 | // 计算属性 |
1 | 总结:计算属性使用场景: |
七、侦听器
vue
中想监听数据的变化,应该怎么办?(监听器)
通过watch
可以侦听到数据的变化。
我们可以在setup
函数中使用watch
函数创建一个侦听器。监听响应式数据的变化,然后执行一个回调函数,可以获取到监听的数据的新值与旧值。
watch
的三个参数
第一个参数:要监听的数据
第二个参数:监听到的数据变化后执行的函数,这个函数有两个参数,分别是新值和旧值。
第三个参数:选项对象,deep
(深度监听)和immediate
(立即执行)
Watch
函数的返回值是一个函数,作用是用来取消监听。
基本使用:
1 | <template> |
在上面的代码中,setup
函数内,定义两个响应式对象分别是question
与answer
,这两项的内容都是字符串,所以通过ref
来创建。使用watch
来监听quest
的变化,由于question
与文本框进行了双向绑定,当在文本框中输入内容后,question
的值会发生变化,发生变化后,就会执行watch
的第二个参数,也就是回调函数。该回调函数内,发送一个异步请求,注意这里使用了fetch
方法,发送请求,该方法返回的是一个promise
对象,所以这里使用了await
,下面调用json
方法获取json
对象,注意该方法返回的也是promise
,所以这里也使用了await
,最后把获取到的数据给了answer
中的value
属性。
1、立即执行
通过上面的案例,我们可以看到,当访问该页面的时候,watch
方法并没有执行。
而是等待数据发生了变化以后,才执行的。
如果想立即执行,需要通过immediate:true
来完成配置。
1 | watch(question,async(newValue,oldValue)=>{ |
这里给watch
函数添加了第三个参数,也是一个对象,在该对象中配置了immediate
2、深度侦听
1 | <template> |
reactive
的数据,用不用deep:true
是没有影响的
非业务需求:尽可能不要直接深度监视 => 相对消耗性能
如果是业务需求,就需要深度监视,就正常监视即可
最后我们把计算属性与侦听器做一个总结,看一下它们的应用场景。
第一点:语境上的差异
watch
适合一个值发生了变化,对应的要做一些其它的事情
而计算属性computed
:一个值由其它的值得来,其它值发生了变化,对应的值也会变化
第二点:计算属性有缓存性,侦听器没有
由于这个特点,我们在实际的应用中,能用计算属性的,会首先考虑先使用计算属性。
第三点:侦听器选项提供了更加通用的方法,适合执行异步操作或者较大开销操作。
八、组件基础
1、为什么使用组件
问题1: 以前遇到重复的结构代码, 怎么做的?
问题2: 复制粘贴? 可维护性高吗 ?
为什么使用组件?
组件的好处: 各自独立, 便于复用,可维护性高
2、什么是组件化开发
1 | 1. 组件是可复用的 Vue 实例, 封装标签, 样式和JS代码 |
问题是,如何确定页面中哪些内容划分到一个组件中呢?
但你如何确定应该将哪些部分划分到一个组件中呢?你可以将组件当作一种函数或者是对象来考虑(函数的功能是单一的),根据[单一功能原则]来判定组件的范围。也就是说,一个组件原则上只能负责一个功能。如果它需要负责更多的功能,这时候就应该考虑将它拆分成更小的组件。
3、如何创建和使用组件
App.vue
是根组件, 这个比较特殊, 是最大的一个根组件。其实里面还可以注册使用其他小组件
使用组件的四步 单页面
1 | 1. 创建组件, 封装要复用的标签, 样式, JS代码 |
在compnents
目录下面创建一个组件:Header.vue
,该组件中的代码
1 | <template> |
在App.vue
组件中使用上面定义的Header
组件
1 | <template> |
4、组件样式处理
默认情况下,写在组件中的样式会 全局生效
,因此很容易造成多个组件之间的样式冲突问题
例如,上一小节案例中,我们在Header.vue
这个子组件中,创建了
1 | div { |
然后在App.vue
这个父组件中使用了Header.vue
这个子组件以后,以上样式不仅对Header.vue
组件起作用,对App.vue
组件也是起作用。
如果在Header.vue
这个组件中定义的样式,只在该组件中起作用,应该怎样处理?
可以给组件加上 scoped
属性, 可以让样式只作用于当前组件
1 | <style lang="less" scoped> |
scoped
原理
1 | (1)当前组件内标签都被添加 data-v-hash值 的属性 |
最终效果: 必须是当前组件的元素, 才会有这个自定义属性, 才会被这个样式作用到
九、组件通信
每个组件有自己的数据
每个组件数据独立, 组件数据无法互相直接访问 (合理的)
但是如果需要跨组件访问数据, 怎么办呢? => 组件通信
组件通信的方式有很多: 现在先关注两种, 父传子 子传
1、组件通信 - 父传子
首先明确父和子是谁
父传子语法
1 | <Son price="100" title="不错" :info="msg"></Son |
App.vue
组件中的代码,如下所示:
1 | <Header msg="hello" :title="title"></Header> |
1 | setup(){ |
Header.vue
子组件中的代码
1 | <script> |
1 | <div class="hm-header"> |
总结:
1 | 父传子的基本步骤是什么? |
1 | 以后的数据,肯定是后台回来的,商品应该是动态渲染的 |
2、v-for
遍历展示组件练习
在components
目录下面创建MyProduct.vue
组件,该组件中的代码:
1 | <template> |
App.vue
组件中的代码
1 | import MyProduct from './components/MyProduct.vue'; |
注册组件
1 | components:{ |
准备数据源
1 | const list =reactive({ |
修改模版中的代码
1 | <hr/> |
3、单向数据流
在vue中需要遵循单向数据流原则: (从父到子的单向数据流动, 叫单向数据流)
- 父组件的数据变化了,会自动向下流动影响到子组件
- 子组件不能直接修改父组件传递过来的
props, props
是只读的!
4、组件通信 - 子传父
需求: 商品组件, 实现砍价功能
给商品这个子组件,添加一个砍价
的按钮,当单击该按钮的时候,将对应商品的价格减去对应的值。
注意:商品的价格是在父组件中。
这就需要子组件向父组件中传递所砍价的钱数,以及商品的编号,这样在父组件中才会找到对应的商品,然后减去对应的钱数。
这就是子组件向父组件中传递数据。
修改/components/MyProduct.vue
子组件中的代码
1 | <template> |
添加了砍价
按钮
setPrice
方法的实现
1 | // 第一个参数是props |
通过emit
方法,触发父组件传递过来的setPrice
事件,同时传递了响应的参数
下面修改父组件App.vue
1 | <MyProduct v-for="item in productList" |
这里为子组件MyProduct
指定了事件setPrice
事件,当子组件通过emit
方法触发了该事件以后,会调用handleSetPrice
方法,该方法中的实现代码如下所示:
1 | // 砍价 |
5、组件通信–兄弟组件通信
这里通过mitt
这个第三方包来实现。
1 | npm i mitt |
第一:创建mitt
的实例。
在src
下面创建untils
目录,在该目录中创建 mitt.js
文件,该文件中的代码:
1 | import mitt from 'mitt' |
第二:在components/Header.vue
组件中触发事件
1 | <div class="hm-header"> |
1 | const handleClick =()=>{ |
import mitt from '@/utils/mitt
‘
第三:在components/MyProduct.vue
中处理事件
1 | import {onMounted}from 'vue' |
1 | const someMethod=(num)=>{ |
十、组件进阶
1、获取Dom
元素
KilgourNote:
给标签加ref属性可以获取dom对象,
给组件标签加可以获取组件实例
在 Vue
中 如何获取真实DOM
元素呢 ?
利用ref
可以用于 获取 dom
元素
基本使用:
1 | <template> |
当单击按钮的时候,可以获取到对应的Dom
元素。
1 | const handleGet=()=>{ |
这里给获取到的Dom
元素设置了背景色。
通过ref属性获取组件对象
在components
目录下面创建Demo.vue
组件,该组件中的代码如下所示:
1 | <template> |
修改获取Dom元素.vue
组件中的代码
1 | import Demo from './components/Demo.vue' |
导入Demo
组件,并且完成组件的注册
1 | <Demo ref="demo"></Demo> |
在模版中使用了Demo
组件,并且添加一个button
按钮
1 | setup(){ |
创建了demo
对象,与Demo
组件通过ref
属性进行关联。
1 | const handldeDemoGet=()=>{ |
在handleDemoGet
方法中,可以调用Demo
组件中的hanldeMethod
方法。
2、nextTick
函数
判断如下代码的输出结果?
1 | const count = ref(0); |
模版中的代码:
1 | <p ref="pcount">数字:{{ count }}</p> |
当单击计算
按钮的时候,是可以完成count
值的累加。
但是问题是:在浏览器的控制台中输出的是之前的dom
,造成这一问题的原因是:Vue
在更新Dom
的时候是异步的
。
也就是说,在更新完count
这个状态数据以后,不是立即更新对应的dom
元素。
但是,我们想更新完count
这个状态数据库以后,立即获取到最新的dom
元素,应该怎样处理呢?
这就需要使用到nextTick
函数,如下代码:
1 | import {ref,nextTick} from 'vue' |
nextTick(函数体)
:等DOM更新后, 才会触发执行此方法里的函数体
3、dynamic
动态组件
动态组件是什么?
是 可以改变 的 组件
动态组件能解决什么需求呢 ?
解决多组件同一位置, 切换显示的需求
思考: 需要2个组件, 互斥的显示隐藏切换, 应该怎么做?
可以使用v-show
,但是使用动态组件
更加简单。
基本语法:
1 | 1. component 组件(位置) + is 属性 (哪个组件) |
具体代码演示:
在components
目录下面创建UserAccount.vue
组件,代码如下:
1 | <template> |
再创建UserInfo.vue
组件,代码如下所示:
1 | <template> |
然后在src
目录下面创建动态组件.vue
这个组件,代码如下所示:
1 | <template> |
4、插槽
需求: 要在页面中显示一个对话框, 封装成一个组件
问题:组件的内容部分,不希望写死,希望能使用的时候自定义。怎么办?
(1)父传子: 父传子固然可以完成一定层面的组件文本的定制(可以用于传值), 但是自定义性较差, 且无法自定义结构。
(2)如果希望能够自定义组件内部的一些结构 => 就需要用到插槽(可以传递结构)
4.1 默认插槽
基本使用
默认插槽:没有起名字,默认名字叫defalut
下面看一下插槽的基本使用。
在components
目录下面创建Dialog.vue
组件,该组件中的代码如下所示:
1 | <template> |
在插槽.vue
组件中使用上面创建的Dialog.vue
组件,代码如下所示:
1 | <template> |
在使用Dialog
组件的时候,指定了内容。
在Dialog.vue
组件中,可以指定插槽中默认显示的内容
1 | <div class="dialog-header"> |
这样在插槽.vue
组件中,如果不指定内容,显示插槽中默认的内容,如果指定了内容,就展示所指定的内容。
4.2 具名插槽
组件内有 2处以上 的内容,不确定怎么办?
可以自定义名字,通过name
自定义名字,可以实现定向分发
需求:一个组件内有多处,需要外部传入标签,进行定制
语法:
1 | 语法: |
Dialog.vue
组件中的代码:
1 | <template> |
添加了两个具名插槽,分别是header
和footer
修改插槽.vue
组件中的代码,如下所示:
1 | <template> |
在 Vue 中,很多指令都有简写形式,v-slot:name 指令也有简写形式,比如看我们下面的示例。
原写法:
1 | <template v-slot:footer> |
简写
1 | <template #footer> |
4.3 作用域插槽
我们仔细思考插槽的使用,会发现这有一点类似于父子组件传递,只不过插槽传递的是模板内容罢了。那么涉及到传值,就会有一个作用域的问题
作用域插槽: 定义 slot
插槽的同时, 是可以传值的。给插槽上可以绑定数据,将来使用组件时可以用。
修改Dialog.vue
组件
1 | <slot title="标题1" msg = "hello title"> |
这里指定了title,msg
两个属性
在插槽.vue
组件中
1 | <template> |
通过v-slot
来指定了一个值slotProps
,它是一个对象,该对象中存储了对应的数据。
我们都知道对象是可以解构的,所以我们在父组件(插槽.vue
)中还可以直接使用解构的写法来获取数据。
1 | <h3>友情提示</h3> |
十一、生命周期
1、生命周期基本认知
vue
组件生命周期:从创建 到 销毁 的整个过程就是 – Vue
实例的 - 生命周
2、生命周期探讨
1 | 在这一小节中,我们看一下`vue`生命周期中其它的一些钩子函数内容。 |
1 | <template> |
在上面的代码中,我们将所有的钩子函数都添加上了,然后打开浏览器,看下执行结果:
1 | beforCreate |
beforeCreate
: Vue
实例初始化之后,以及事件初始化,以及组件的父子关系确定后执行该钩子函数,一般在开发中很少使用
created
: 在调用该方法之前,初始化会被使用到的状态,状态包括props
,methods
,data
,computed
,watch
.由于data
数据已经初始化,所以可以获取到组件的数据。
beforeMount
:在执行该钩子函数的时候,虚拟DOM
已经创建完成,马上就要渲染了,经过这一步后,在模板中所写的{{foo}}
会被具体的数据所替换掉。
所以下面执行mounted
的时候,可以看到真实的数据。同时整个组件内容已经挂载到页面中了,数据以及真实DOM
都已经处理好了,可以在这里操作真实DOM
了,也就是在mounted
的时候,页面已经被渲染完毕了,在这个钩子函数中,我们可以去发送ajax
请求。
下面看一下更新的操作
1 | <div> |
1 | methods:{ |
这里在data
函数中定义了foo
,然后单击更新
按钮的时候,调用update
函数完成foo
的值更新。
1 | 当整个组件挂在完成后,有可能会进行数据的修改,当`Vue`发现`data`中的数据发生了变化,会触发对应组件的重新渲染,先后调用了`beforeUpdate` 和`updated`钩子函数。 |
销毁:
beforeUnmount,unmounted
Vue
实例已经被销毁,所有的事件监听器会被移除,所有的子实例也会被销毁。
setup
是在组件初始化之前执行的,是在beforeCreate
与created
之间执行的,所以beforeCreate
与created
的代码都可以放到setup
函数中。
所以,在上图中,我们可以看到beforeCreate
与created
在setup
中没有对应的实现。
其它的都是在钩子函数名称前面添加上on
,并且首字母大写。
注意:onUnmounted
类似于之前的destoryed
,调用组件的onUnmounted
方法会触发unmounted
钩子函数
十二、路由
1、单页应用程序: SPA - Single Page Application
具体使用示例: 网易云音乐 https://music.163.com
单页面应用(SPA
): 所有功能在一个html
页面上实现 (多页面应用程序MPA
)
1 | 优点: |
2、路由的介绍
前端Vue
中的路由是什么
路径 和 组件的映射关系
路径 和 组件的切换匹配, 怎么实现呢?
vue-router
: vue-router
本质是vue
官方的一个路由插件,是一个第三方包
官网: https://router.vuejs.org/zh
vue-router
集成了 路径 和 组件的切换匹配处理,我们只需要配置规则即可。
在具体学习vue-router
如何使用之前,我们需要认知 组件的两种分类
组件分类: .vue文件分2类, 一个是页面组件, 一个是复用组件
src/views
文件夹: 页面组件 - 页面展示 - 配合路由用
src/components文件夹
:复用组件 - 展示数据 - 常用于复用
3、vue-router
基本使用
1 | npm install vue-router |
首先在项目中单的src
目录下面创建一个views
文件夹,在该文件夹中创建以下页面组件
find.vue
1 | <template> |
my.vue
1 | <template> |
part.vue
1 | <template> |
在src
目录下面router
目录,在router
目录下面创建index.js
文件,该文件中的代码
1 | import { createRouter, createWebHashHistory } from "vue-router"; |
在main.js
入口文件中导入以上创建的路由对象,并且挂在到Vue
实例中
1 | import { createApp } from 'vue' |
在App.vue
中添加路由出口
1 | <template> |
启动项目进行测试。
4、声明式导航 - 跳转传参
目标:在跳转路由时, 可以给路由对应的组件内传值
在router-link
上的to
属性传值, 语法格式如下
1 | /path?参数名=值 |
1 | <router-link to="/my?id=1">我的音乐</router-link> |
这里传递了参数id
,表示音乐的编号。
看一下my.vue
组件中怎样接收参数?
1 | <p>我的音乐:{{ $route.query.id }}</p> |
在setup
函数中,通过如下方方式接收参数
1 | <script> |
下面再来看另外一种传递参数的情况
1 | <router-link to="/find/10">发现音乐</router-link> |
修改App.vue
组件中的链接
修改router/index.js
定义的路由规则
1 | const routes =[ |
在配置路由规则的时候,需要指定对应的参数。
修改views/find.vue
组件中的代码,在该组件中接收传递过来的参数
1 | <template> |
在模版中通过$route.params
来接收传递过来的参数
同理setup
入口函数中的处理如下所示:
1 | <script> |
5、重定向
匹配path
后, 强制跳转path
路径
修改router/index.js
中所定义的路由规则:
1 | import { createRouter, createWebHashHistory } from "vue-router"; |
当在浏览器中输入地址:http://localhost:8080/#/
的时候,直接调整到/my
6、404
问题: 当访问不存在的页面应该要显示什么呢?
404:当找不到路径匹配时,给个提示页面
在router/index.js
文件中的routes
数组中添加如下路由规则
1 | { |
在src/views
目录中创建NotFound.vue
组件
1 | <template> |
7、编程式导航
编程式导航:用JS
代码来进行跳转
语法: path
或者name
任选一个 // location.href=”/home/index”
修改views/find.vue
组件中的代码
1 | <p>发现音乐</p> |
1 | // 这里需要导入useRouter |
或者采用如下写法:
1 | const handleClick=()=>{ |
下面通过name
的方式实现跳转
1 | const handleClick=()=>{ |
当然在编程式导航的时候,可以进行参数的传递。
1 | const handleClick=()=>{ |
这里是通过query
的方式来进行传参的在my.vue
组件中通过$route.query.id
方式来进行接收。
8、路由嵌套
在现有的一级路由下, 再嵌套二级路
二级路由示例-网易云音乐-发现音乐下 https://music.163.com
1 | 1. 创建需要用的所有组件 |
在views
目录下面创建second
目录,并且在该目录下面创建以上对应的组件
修改router/index.js
文件中所定义的路由规则
1 | const routes =[ |
在find.vue
组件中添加router-view
1 | <template> |
1 | http://localhost:8080/#/find/10/recommend |
输入以上地址进行测试。
有可能会出现文件名称的错误,所以在vue.config.js
文件中关闭检测功能
1 | const { defineConfig } = require('@vue/cli-service') |
参考:https://blog.csdn.net/qq_57587705/article/details/124674660
以上是关于路由的基本应用,关于其他的应用,在项目中会进行讲解。
十三、Vuex
1、vuex
概述
vuex
是vue
的状态管理工具,状态即数据。 状态管理就是集中管理vue
中 通用的 一些数据
注意(官方原文):https://vuex.vuejs.org/zh/guide/
- 不是所有的场景都适用于
vuex
,只有在必要的时候才使用vuex
- 使用了
vuex
之后,会附加更多的框架中的概念进来,增加了项目的复杂度 (数据的操作更便捷,数据的流动更清晰)
vuex
的优点: 方便的解决多组件的共享状态
它是独立于组件而单独存在的,所有的组件都可以把它当作 一座桥梁 来进行通讯。
特点:
- 响应式: 只要仓库一变化,其他所有地方都更新 (太爽了!!!)
- 操作更简洁
什么数据适合存到vuex
中
一般情况下,只有 多个组件均需要共享的数据 ,才有必要存储在vuex
中,
对于某个组件中的私有数据,依旧存储在组件自身的data中。
例如:
- 对于所有组件而言,当前登陆的 用户信息 是需要在全体组件之间共享的,则它可以放在vuex中
- 对于文章详情页组件来说,当前的用户浏览的文章列表数据则应该属于这个组件的私有数据,应该要放在这个组件data中。
2、Vuex
基本使用
1 | Vuex是专门为Vue.js设计的状态管理库。 |
在这里我们先来看一下vuex
的基本使用
在store/index.js
文件中,添加了如下代码:
1 | npm install vuex |
在src
目录下面创建store
目录,在该目录下面创建index.js
文件,代码如下所示:
1 | import { createStore } from "vuex"; |
在仓库中添加了一个状态数据,username
。
下面就可以在组件中获取对应的状态数据。(创建一个App.vue
组件)
1 | <template> |
在main.js
文件中使用vuex
1 | import { createApp } from 'vue' |
这里为了方便测试,暂时将路由注释掉。
如果在setup
入口函数中获取状态数据,可以采用如下的写法:
1 | <script> |
3、getters
用法
getters
的作用是对state
属性进行计算,类似于vue
中的计算属性,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
例如把username
中的数据倒序输出,或者过滤商品数据等。
在store/index.js
文件中,添加对应的getters
代码,如下所示:
1 | import { createStore } from "vuex"; |
返回到App.vue
模板中使用:
1 | <template> |
在上面的代码中,通过$store.getters.newName
获取getters
中的数据,或者是采用如下写法:
1 | <p> |
newName
本身就是对象中的一个属性。所以可以采用如上的写法。
在setup
入口函数中可以采用如下的写法:
1 | setup(){ |
4、mutations
用法
状态数据的修改必须提交Mutaion
,并且Mutaion
的修改必须是同步执行的。
继续修改store/index.js
文件中的代码,如下所示:
1 | import { createStore } from "vuex"; |
在上面的mutations
中,定义了updateName
方法来修改state
状态中的username
属性的值。
下面返回到App.vue
中,我们来看一下怎样使用mutations
中的updateName
方法完成状态更新操作。
1 | <template> |
在模板中,我们添加了一个提交按钮
,单击该按钮执行mutaionsFn
这个函数。
该函数的实现如下所示:
1 | <script> |
在mutationsFn
函数中,我们通过store.commit
提交了一个updateName mutations
,完成用户名的更新操作。
注意:这里需要将mutationsFn
方法返回,在模板中才能使用。
5、actions
用法
actions
的作用:
如果需要执行异步操作,我们需要通过Action
来完成。
如果异步执行完毕后,需要修改状态,需要提交Mutation
来修改State
状态。因为所有的状态的修改都需要通过Mutation
来完成。
例如:我们需要异步获取商品数据,就需要在Action
中发送请求,异步完成后,需要提交Mutation
,把数据记录到State
中。
下面修改store/index.js
中的代码:
1 | import { createStore } from "vuex"; |
在上面的代码中,我们首先定义了一个count
状态属性
在actions
中定义了一个increateAsync
方法,该方法中通过setTimeout
进行异步请求的模拟,在1秒中以后提交increate
这个mutations
修改count
的状态,这里传递了参数playload
在mutations
中定义了increate
方法,完成了count
这个状态值的更新。
返回到App.vue
中。
1 | <p> |
展示了count
这个状态值
1 | const mutationsFn = () => { |
在mutationsFn
方法中,通过store.dispatch
来派发increateAsync
方法,并且传递的参数是6.
以上就是Vuex
的基本使用。
6、模块基本使用
这节课我们来看一下Module
模块的内容。
如果我们把所有的状态都存储到了State
中,后期会变得非常难维护,Store
也就变得非常臃肿,为了解决这个问题,Vuex
允许我们将Store
分隔成模块。每一个模块都有自己的State
,Mutation
,Action
,Getter
等。当状态内容非常多的时候,Module
是非常有用的。
例如:我们这里把用户的数据放到User
模块中,购物车中的数据放到Cart
模块中等等。
下面我们先来定义模块的内容,当模块定义好以后,还需要在Store
中进行注册。
修改store/index.js
文件中的代码,如下所示:
在下面的代码中,我们定义了A
模块和B
模块,这两个模块中都定义了state
和getters
并且B模块中开启了命名空间,A模块没有开启命名空间。
最后把两个模块都在createStore
方法中的modules
进行了注册。
1 | import { createStore } from "vuex"; |
下面,返回到App.vue
组件中,使用一下(将原有的App.vue
组件进行备份)
1 | <template> |
要获取A
模块中的state
数据的时候,需要添加上模块的名字,但是在获取getters
中的内容的时候不需要添加模块的名字,如果添加了模块的名字,会出错。
原因:由于A模块没有添加命名空间,所以除了state
需要添加模块名称,模块内部的getters
,action
,mutaions
都是注册在全局下的。
下面我们再来看一下模块B的访问
1 | <template> |
在使用B
模块中的state
的时候需要添加模块
名称。
这里重点需要注意的是:在访问B
模块中的getters
的时候的写法。
1 | {{ $store.getters["moduleB/newName"] }} |
在方括号中,先写模块的名称,后面再写getters
的名称。
下面我们在把B
模块中的mutations
和actions
都补充一下
1 | import { createStore } from "vuex"; |
通过上面的代码,我们可以看到mutations
和actions
的内容和前面写的是一样的。
下面返回到App.vue
组件中使用一下mutations
和actions
.
1 | <template> |
在上面的代码中,我们添加了count
这个状态,同时添加了两个按钮。
下面看一下当按钮的单击事件触发以后,对应的方法的实现:
1 | <script> |
在上面的代码中,不管是执行commit
或者是dispatch
方法,都指定了模块的名字
,然后后面跟上mutations
的名字或者是actions
的名字。
我们在项目中都需要给模块添加命名空间。
7、Vuex
数据持久化操作
在项目中,我们需要将vuex
中存储的数据持久化到本地,例如:我们在项目中的用户信息需要存储到vuex
中,但是需要持久化到本地,否则刷新浏览器数据会丢失。
还是就是购物车的数据,也是存储到vuex
中,当然在用户未登录的情况是可以向购物车中添加数据的,而vuex
中存储的购物车中的商品数据也需要持久化到本地。
关于对vuex
中的数据持久化的操作,这里我们使用vuex-persistedstate
插件来实现
1 | npm i vuex-persistedstate |
下面在src/store
目录下面创建modules
文件夹,在该文件夹下面创建cart.js
和user.js
两个模块文件。
cart.js
文件中的代码如下所示:
1 | // 购物车模块 |
这里我们定义了cart
这个模块,并且开启了命名空间。注意这里的state
是一个方法,这是语法上的要求。最终返回的是购物车中的商品列表数据。
user.js
文件中定义的内容如下所示:
1 | // 用户模块 |
这里也是开启了命名空间,同时用户的信息存储到了profile
对象中,该对象中定义的属性与服务端接口返回的内容是一致的。
这一点,后面讲解到的时候,我们可以再来看一下。
下面在添加一个category.js
文件,代码如下所示:
1 | // 商品分类模块 |
下面在修改store/index.js
文件中的代码,如下所示:
1 | import { createStore } from "vuex"; |
在上面的代码中完成了模块的注册操作。
在进行持久化之前,我们先来做一个测试,看一下当刷新浏览器以后,vuex
容器中的数据是否会丢失。
1 | // 用户模块 |
我们在store/modules/user.js
文件中添加了一个mutations
方法setUser
,该方法的作用就是修改用户信息。
下面返回到App.vue
这个组件中(这里将原来的App
组件重新命名,这里创建一个新的App.vue
组件),该组件中的代码如下所示:
1 | <template> |
在模板中先获取user
模块中的profile
对象中的account
用户名数据。
下面单击按钮以后,完成account
属性值的修改,注意commit
方法在提交的时候,需要指定模块的名称以及对应的mutations
中的方法名。
返回到浏览器中进行测试,当单击了按钮以后,页面中展示了zhangsan
这个用户名,但是当刷新后,用户名信息丢失了。
所以下面需要对vuex
中的数据实现持久化的操作。
要想持久化,我们可以把vuex
中的数据持久化到localStorage
中,但是问题是,每次写关于操作localStorage
的代码也是非常麻烦的。
所以这里我们可以通过 vuex-persistedstate
这个插件来实现。
这个插件安装好以后,下面看一下怎样使用?
在store/index.js
文件中配置该插件
1 | import { createStore } from "vuex"; |
在上面的代码中,我们导入了vuex-persistedstate
这个插件,并且指定了存储到本地的数据的名字,以及要存储的模块。
下面返回到浏览器中进行测试,当单击了按钮以后,完成了数据的更新,然后刷新浏览器发现数据没有丢失。
数据都存储到了localStorage
中了,这里可以看一下。