vue总结
VUE基础
一、什么是vue
官方给出的概念是:Vue(类似于View)是一套用于构建用户界面
的前端框架
- 构建用户界面
- 用Vue往html页面中填充数据,非常的方便
- 框架
- 框架是一套现成的解决方案,程序员只能遵守框架的规范,去编写自己的业务功能
- 要学习的vue,就是在学习vue框架中规定的用法!
- vue的指令、组件(是对UI结构的复用)、路由、Vuex、Vue组件库
二、Vue的两个特性
-
数据驱动视图
- 数据的变化会驱动视图自动更新
- 好处是:程序员只管把数据维护好,那么页面结构就会被vue自动渲染出来
-
数据的双向绑定
在网页中。form表单负责采集数据,ajax负责的是提交数据
- JavaScript数据的变化,会自动渲染到页面上
- 页面上表单采集的数据发生变化的时候,会被vue自动获取到,并且更新到js数据里面
注意:数据驱动视图和双向数据绑定的底层原理是
MVVM
(Mode 数据源、View 视图、ViewModel 就是 vue 的实例)
三、vue的指令
-
内容渲染的指令
V-text
:指令的缺点:它会覆盖元素内部原有的内容
<!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 --> <div id="app"> <p v-text="username"></p> <p v-text="gender">性别:</p> </div> <!-- 1. 导入 Vue 的库文件,在 window 全局就有了 Vue 这个构造函数 --> <script src="./lib/vue-2.6.12.js"></script> <!-- 2. 创建 Vue 的实例对象 --> <script> // 创建 Vue 的实例对象 const vm = new Vue({ // el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器 el: '#app', // data 对象就是要渲染到页面上的数据 data: { username: 'zhangsan', gender: '女', info: '<h4 style="color: red; font-weight: bold;">欢迎大家来学习 vue.js</h4>' } }) </script>
视图效果如下:
{{ }}
差值表达式:在实际的开发中使用的最多,只是内容的占位符,不会覆盖原来的内容
<!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 --> <div id="app"> <p>姓名:{{username}}</p> <p>性别:{{gender}}</p> </div> <!-- 1. 导入 Vue 的库文件,在 window 全局就有了 Vue 这个构造函数 --> <script src="./lib/vue-2.6.12.js"></script> <!-- 2. 创建 Vue 的实例对象 --> <script> // 创建 Vue 的实例对象 const vm = new Vue({ // el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器 el: '#app', // data 对象就是要渲染到页面上的数据 data: { username: 'zhangsan', gender: '女', info: '<h4 style="color: red; font-weight: bold;">欢迎大家来学习 vue.js</h4>' } }) </script>
页面效果:
V-html
指令的作用是:可以把带有标签的字符串,渲染成真正的HTML里面的内容
<!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
<div id="app">
<div v-text="info"></div>
<div>{{ info }}</div>
<div v-html="info"></div>
</div>
<!-- 1. 导入 Vue 的库文件,在 window 全局就有了 Vue 这个构造函数 -->
<script src="./lib/vue-2.6.12.js"></script>
<!-- 2. 创建 Vue 的实例对象 -->
<script>
// 创建 Vue 的实例对象
const vm = new Vue({
// el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
el: '#app',
// data 对象就是要渲染到页面上的数据
data: {
username: 'zhangsan',
gender: '女',
info: '<h4 style="color: red; font-weight: bold;">欢迎大家来学习 vue.js</h4>'
}
})
</script>
页面效果:
2. 属性绑定指令
注意:差值表达式只能用在元素的内容节点中,
不能使用在元素的属性节点中!
- 在vue中,可以使用v-bind:指令,为元素的属性动态绑定值,
- 简写是英文的冒号 (:)
- 在使用V-bind属性绑定期间,如果绑定内容需要进行动态的拼接,则字符串的外面应该包裹单引号,例如下面的代码:
<div :title="'box'+index">这是一个div</div>
3. 事件绑定
v-on
:的简写是 @- 语法格式如下代码:
<button @click="add">点击按钮</button>
<h1>
{{count}}
</h1>
<script>
const vm= new Vue({
//el:的值是把vue挂载到哪里
el:'#app',
data:{
count:3
},
methods:{
add(){
//如果在方法中要修改data中的数据,可以通过this访问到
this.count+=1
}
}
})
</script>
$event
的应用场景:如果默认的事件对象 e 被覆盖了,则可以手动传递一个 $event。例如:
<button @click="add(3,$event)">点击按钮</button>
<h1>
{{count}}
</h1>
<script>
const vm= new Vue({
//el:的值是把vue挂载到哪里
el:'#app',
data:{
count:3
},
methods:{
add(n,e){
//如果在方法中要修改data中的数据,可以通过this访问到
this.count+=1
}
}
})
</script>
4.事件修饰符
-
.prevent
<!-- 只有修饰符 --> <a @click.prevent="xxx">链接</a>
-
.stop
<!-- 阻止单击事件继续传播 --> <button @click.stop="xxx">按钮</button>
注意:使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用
v-on:click.prevent.self
会阻止所有的点击,而v-on:click.self.prevent
只会阻止对元素自身的点击。
4.v-model指令
- input输入框
- type=“radio”
- type=“checkbox”
- type=“tel”
- textarea
- select
5.条件渲染页面
v-show
:的原理是,动态为元素添加或者移除display:none
样式,来实现元素的显示或者隐藏- 如果要频繁的切换元素的显示状态,使用
v-show
的性能会更好
- 如果要频繁的切换元素的显示状态,使用
v-if
的原理是:每次动态创建或者移除元素,实现元素的显示或者隐藏- 如果刚进入页面的时候,某些元素默认不需要被提示,而且后期这个元素很可能也不需要被展示出,这个时候 v-if的性能更好
在实际开发的中,大多数情况,不用考虑性能的问题,直接使用v-if就好了!!!
v-if
的指令在使用的时候,有两种方式:
- 直接给一个布尔值 true或者是 false
<p v-if="true">被 v-if 控制的元素</p>
- 给 v-if 提供一个判断条件,根据判断的结果是 true 或 false,来控制元素的显示和隐藏
<p v-if="type === 'A'">良好</p>
四、侦听器
1.什么是watch侦听器
答:watch侦听器允许开发者监视数据的变化,从而针对数据的变化做特定的操作
语法格式如下:
const vm=new Vue({
el:'#app',
data:{username:''},
watch:{
//监听username值的变化
//newVal是变化后的新值,oldVal是“变化之前的旧值”
userName(newVal,oldVal){
console.log(newVal,oldVal)
}
}
})
2.如何使用watch检测用户名是否可以使用
在,监听username的值发生变化的同时,并使用axios发起Ajax请求,检测当前用户名是否可以使用
<!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">
<input type="text" v-model="username">
</div>
<script src="./lib/vue-2.6.12.js"></script>
<script src="./lib/axios.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
username: ''
},
watch: {
// newVal是最新的值
async username(newVal) {
if (newVal === '') return
// 使用axios发起请求,判断用户名是否可以使用
const {
data: res
} = await axios.get('https://www.escook.cn/api/finduser' + newVal)
console.log(res)
}
}
})
</script>
</body>
</html>
3.自动调用侦听器
在默认的情况下,组件在初次加载完毕后不会调用watch侦听器,如果想让watch侦听器立即被调用,则需要使用immediate
选项。示例代码如下:
<!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">
<input type="text" v-model="username">
</div>
<script src="./lib/vue-2.6.12.js"></script>
<script src="./lib/axios.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
username: ''
},
watch: {
// newVal是最新的值
async username(newVal) {
if (newVal === '') return
// 使用axios发起请求,判断用户名是否可以使用
const {
data: res
} = await axios.get('https://www.escook.cn/api/finduser' + newVal)
console.log(res)
}
// 表示页面初次渲染好之后,就立即触发当前的watch侦听器
immediate:true
}
})
</script>
</body>
</html>
4.侦听器侦听的是一个对象
如果watch侦听的是一个对象,如果对象中的属性值发生了变化,则无法being监听到,这个时候我们通常会使用deep
选项,示例代码如下:
<!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">
<input type="text" v-model="username">
</div>
<script src="./lib/vue-2.6.12.js"></script>
<script src="./lib/axios.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
info:{username: 'admin'}
},
watch:{
info:{
handlet(newVal){
console.log(newVal.username)
},
deep:true
}
}
})
</script>
</body>
</html>
5.如果监听对象单个属性的变化
如果只想监听对象中单个属性的变化,则可以按照如下的方式定义watch侦听器,
<!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">
<input type="text" v-model="username">
</div>
<script src="./lib/vue-2.6.12.js"></script>
<script src="./lib/axios.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
info:{username: 'admin'}
},
watch:{
'info.username':{
handler(newVal){
console.log(newVal)
}
}
}
})
</script>
</body>
</html>
五、计算属性
1.计算属性的基本使用
计算属性指的是通过一系列运算
之后,最终得到一个属性值,这个动态计算出来的属性值可以被模板结构或methods方法使用,实例代码如下:
<!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">
<input type="text" v-model="show">
</div>
<script src="./lib/vue-2.6.12.js"></script>
<script src="./lib/axios.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
r:0,g:0,b:0
},
computed:{
rgb(){return `rgb(${this.r},${this.g},${this.b})`}
},
methods:{
show(){
console.log(this.rgb)
}
}
})
</script>
</body>
</html>
2.计算属性的特点
① 虽然计算属性在声明的时候被定义成为 方法,但是计算属性的本质是一个属性
② 计算属性会缓存计算的结果,只有计算属性 依赖的数据发生变化的时候,才会重新进行运算
六、vue-cli
1.什么是单页面应用程序
单页面应用程序(英文名:Single Page Application),简称SPA,顾名思义,指的就是一个Web网站中只有唯一的一个HTML页面
,所有的功能,与交互都在这唯一的一个页面内部完成。
2.什么是vue-cli
答:vue-cli是Vue.js开发的标准工具,它简化了程序员基于webpack创建工程化的Vue项目的过程,引用来自Vue-cli官网中的一句话:程序员可以**专注在撰写应用上
**,而不必花好几天去纠结webpack配置的问题
Vue-cli中文官网:https://cli.vuejs.org/zh/
3.vue-cli的安装和使用
Vue-cli是在npm上的一个全局包,使用 npm install
命令,接口方便的把它安装到自己的电脑上;
npm install -g @vue/cli
基于vue-cli快速生成工程化的vue项目:
vue create 项目的名称
4.vue-cli中项目的运行流程是什么样的
在工程化的项目中,vue要做的事情很单纯,通过main.js把App.vue渲染到index.html的指定的区域中,其中
- App.vue用来编写待渲染的模板结构
- index.html中需要预留一个el的区域
- main.js是吧App.vue渲染到index.html所预留的区域中
七、Vue组件
1.什么是组件化的开发
组件化开发指的是:根据封装的思想,把页面上可重复使用的UI结构 封装为组件,从而方便项目的开发与维护。
2.vue中组件的开发
Vue是一个支持组件化开发的前端框架,vue中规定:组件的后缀名是:.vue。之前接触到的App.vue文件本质上就是一个Vue的组件。
3.vue组件的组成
每一个组件都是由3部分构成的,分别是
- temmplate—>组件的模板结构
- script---->组件的JavaScript行为
- style---->组件的样式
其中,每个组件中必须包含template模板结构,,而script行为和style样式都是可选的组成部分
1). template
vue规定:每个组件对应的模板结构,需要定义到节点中
<template>
<!--当前组件的DOM结构,需要定义到template标签的内部-->
</template>
注意:
-
template 是vue提供的容器标签,只起到包裹性质的作用,它不会被渲染为真正的DOM元素
-
在Vue2中template只可以写一个根节点
-
在Vue3中template中可以写多个根节点
2). script
vue规定:开发者可以在
<script>
// 今后,组件相关 data是存放数据的、methods是存放方法的
//都需要定义到export default所导出的对象中去
export default{
data(){
name:'张三'
},
methods:{
//这是一个点击事件
onAdd(){
console.log('我是第一次学习Vue组件')
}
}
}
</script>
注意:
vue规定:vue组件中的data必须是一个函数,不能直接指向一个数据对象。因此在组件中定义data数据节点的时候,下面的方式是错误的:
data:{ //组件中,不能直接让data指向一个数据对象(会报错)
count:0
}
会导致多个组件实例共用同一份数据的问题,如果有其他问题可以查看vue.js官网
3).style
vue规定了:组件内的style节点是可选的,开发者可以再
<style>
h1{
font-size:20px;
}
</style>
让style中支持Less语法
- 在
<style lang="less">
h1{
font-size:20px;
span{
color:red;
}
}
</style>
4.组件之间的关系
- 第一种组件在被封装好之后,彼此之间是相互独立的,不存在父子关系
- 在使用组件的时候,根据彼此的嵌套的关系,就形成了,父子关系,兄弟关系
1. 使用组件的三个步骤
- 步骤一,使用import 语法导入需要使用的组件
import Left from '@/components/Left.vue'
- 步骤二 使用components节点注册组件
export default{
components:{
Left
}
}
- 步骤三、以标签的形式使用刚才注册的组件
注意:
- 在components中注册的是私有的子组件。例如:在组件A中的components节点下,注册了组件F。这个时候组件F只能在组件A中使用,不能被用在组件C里面
2.注册全局的组件
在vue项目中main.js入口文件中,通过Vue.components()方法,可以注册全局组件,示例代码如下
//导入需要全局注册的组件
import Count from '@/components/Count.vue'
//参数1.字符串格式,表示组件的“注册名称”
//参数2。需要被全局注册的那个组件
Vue.components('MyCount',Count)
5.组件中的Props
props是组件的自定义属性,在封装通用组件的时候,合理的使用props可以极大的提高组件的复用性,它的语法格式如下:
export default {
//组件的自定义属性
props:{'自定义属性1','自定义属性2','其他自定义属性'},
//组件的私有属性
data(){
return{}
}
}
1. props的属性的·读写
在vue中是这样规定的组件中封装的自定义属性是只读的,程序员不能直接修改props的值。否则会直接报错:
要想修改props的值,可以把props的值转存到data中,因为data中的数据是可读可写的!!!
export default{
props:['init'],
data(){
return {
//把this.init的值转存到count
count:this.init
}
}
}
2. props的默认值
在声明自定义的属性的时候,可以通过default来定义属性的默认值,实例代码如下:
export default{
props:{
init:{
//使用default属性来定义属性的默认值
default:0
}
}
}
3.props的值类型
在声明自定义属性的时候,可以通过type来定义属性的值类型。代码如下
export default{
props:{
init:{
//使用default属性来定义属性的默认值
default:0
//使用type属性定义属性的值类型,
// 如果传递过来的值不符合这个类型的,则会在终端上报错
type:Number
}
}
}
4.props的必填选项
在声明自定义属性的时候,我们可以通过required选项,将属性设置为必填项,强制用户必须传递属性的值,示例代码如下:
export default{
props:{
init:{
//值类型为Number数字
type:Number,
//必填项效验
required:true
}
}
}
7.组件之间的样式冲突的问题
默认情况下,写在.vue 组件中的样式会全局生效,因此很容易造成多个组件之间的样式冲突问题
导致组件之间样式冲突的根本原因是:
- 单页面应用程序中,所有的组件的DOM结构,都是基于唯一的index.html页面进行呈现的
- 每个组件中的样式,都会影响整个index.html页面中的DOM元素
1)、如何解决组件样式冲突的问题
为每个组件分配唯一的自定义属性,在编写组件样式时,通过属性选择器来控制样式的作用域,示例代码如下:
<template>
<div class="container">
<h3 data-v-0001>轮播图组件</h3>
</div>
</template>
<style>
/*通过中括号"属性选择器",未防止组件之间的样式,冲突的问题,因为每个组件分配的自定义属性是"唯一的"*/
.container[data-v-0001]{
border:1px solid red;
}
</style>
2)、style节点的scoped属性
为了提高开发的效率和开发体验,vue为style节点提供了scoped属性,从而防止组件之间的样式冲突问题:
<template>
<div class="container">
<h3>轮播图组件</h3>
</div>
</template>
<style scoped>
/*style节点的scped属性,用来自动为每个组件分配唯一的:"自定义属性",并且自动为当前组件到的DOM标签和style样式应用这个自定义属性,防止组件的样式冲突问题*/
.container{
border:1px solid red;
}
</style>
3)、/depp/
的样式穿透
如果给当前组件的style节点添加了scoped属性,则当前组件的样式对其子组价是不生效的,如果想让某些样式对子组件生效,可以使用/deep/深度选择器
<template>
<div class="container">
<div class="title">
我是标题
</div>
</div>
</template>
<style scoped lang="less">
.title{
color:blue; /* 不加/deep/的时候,生成的选择器的格式为 .title[data-v-052242de]*/
}
/deep/ .title{
color:blue;/* 加上/deep/的时候,生成的选择器的格式为 [data-v-052242de].title*/
}
</style>
八、生命周期
1.生命周期&生命周期函数
生命周期
(Life Cycle)是一个组件从创建–>运行–>销毁的整个阶段,强调的是一个时间段
。而生命周期函数:是由vue框架提供的内置函数,会伴随着组件的生命周期,自动按次序依次执行·。
注意:生命周期强调的是时间段,生命周期函数强调的是时间点
2. 组件生命周期函数的分类
beforeCreate
(创建前) 在数据观测和初始化事件还未开始
created
(创建后) 完成数据观测,属性和方法的运算,初始化事件,$el属性还没有显示出来
beforeMount
(载入前) 在挂载开始之前被调用,相关的render函数首次被调用。实例已完成以下的配置:编译模板,把data里面的数p据和模板生成html。注意此时还没有挂载html到页面上。
mounted
(载入后) 在el 被新创建的 vm.$el替换,并挂载到实例上去之后调用。实例已完成以下的配置:用上面编译好的html内容替换el属性指向的DOM对象。完成模板中的html渲染到html页面中。此过程中进行ajax交互。
beforeUpdate
(更新前) 在数据更新之前调用,发生在虚拟DOM重新渲染和打补丁之前。可以在该钩子中进一步地更改状态,不会触发附加的重渲染过程。
updated
(更新后) 在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。调用时,组件DOM已经更新,所以可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。
beforeDestroy
(销毁前) 在实例销毁之前调用。实例仍然完全可用。
destroyed
(销毁后) 在实例销毁之后调用。调用后,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。
下面是流程图
九、组件之间的数据共享
1.组件之间的关系
在项目开发中,组件的最常见的关系分为如下两种:
① 父子关系
② 兄弟关系
2.父子组件之间的数据共享
父向子传值
父组件向子组件共享数据需要使用自定义属性。实例代码如下:
父组件:
<template>
<Son :msg="message" :user="userInfo"></Son>
</template>
<script>
export default{
data:{
return{
message:'hellow Vue.js',
userInfo:{name:'75',age:20}
}
}
}
</script>
子组件:
<template>
<div>
<h5>子组件</h5>
<p>父组件传递过来的msg的值是:{{msg}}</p>
<p>父组件传递过来的user的值是:{{user}}</p>
</div>
</template>
<script>
export default{
props:{
msg:{
type:String,
default:''
},
user:{
type:Object,
default:()=>{}
}
}
}
</script>
子向父传值
子组件向父组件共享数据使用的是 自定义事件
,代码实例如下:
子组件
export default {
data(){
return {count:0}
},
methods:{
add(){
this.count+=1
//修改数据的时候。通过$emit()触发自定义事件
this.$emit('numchange',this.count)
}
}
}
父组件:
<template>
<Son @numchange="getNewCount"></Son>
<p>
{{countFromSon}}
</p>
</template>
<script>
export default{
data:{
return{
countFromSon:0
}
},
methods:{
getNewCount(val){
this.countFormSon=val
}
}
}
</script>
兄弟组件之间的传值
在vue2中,兄弟组件之间数据共享的方案是EventBus
.
兄弟组件A(卖家)
import bus from './eventBus.js'
export default{
data(){
return{
msg:'hello Vue.js'
}
},
methods:{
sendMsg(){
bus.$emit('share',this.msg)
}
}
}
EventBus.js(中间商)
import Vue from 'vue'
//向外共享vue的实例对象
export default new Vue()
兄弟组件C (买家)
import bus from './eventBus.js'
export default{
data(){
return {
msgFromLeft:''
}
},
created(){
bus.$on('share',val=>{
this.msgFromLeft=val
})
}
}
EventBus的使用步骤
- 创建eventBus.js模块,并向外共享一个Vue的实例对象
- 在数据发送方,调用**bus.$emit(‘事件名称’,要发送的数据)**方法触发自定义事件
- 在数据的接收方,调用**bus.$on(‘事件名称’,事件处理函数)**方法注册一个自定义事件
十、ref的引用
1.什么是ref引用
ref是用来辅助开发者在不依赖于JQuery的情况下,获取Dom元素或者组件的调用。
每个vue组件实例上,都包含一个 r e f s 对 象 , 里 面 存 储 着 对 应 的 D O M 元 素 或 组 件 的 引 用 , 默 认 情 况 下 , 组 件 的 refs对象,里面存储着对应的DOM元素或组件的引用,默认情况下,组件的 refs对象,里面存储着对应的DOM元素或组件的引用,默认情况下,组件的refs指向一个空对象
<template>
<div>
<h3>MyRef 组件</h3>
<button @click="getRef">获取$refs引用</button>
</div>
</template>
<script>
export default{
methods:{
getRef(){
//this是当前组件的实例对象,this.$refs默认指向一个空对象
console.log(this)
}
}
}
</script>
2.使用ref引用DOM元素
如果想要使用ref引用页面上的DOM元素,则可以按照如下的方式进行操作:
<!--使用ref属性,为对应的Dom添加引用名称-->
<h3 ref="myh3">MyRef组件</h3>
<button @click="getRef">获取$refs引用</button>
<script>
export default{
methods:{
getRef(){
//通过this.$refs.引用的名称,可以获取到DOM元素的引用
console.log(this.$refs.myh3)
//操作DOM元素,把文本颜色改为红色
this.$refs.myh3.style.color='red'
+ }
}
}
</script>
3.使用 ref 引用组件实例
如果想要使用 ref 引用页面上的组件实例,则可以按照如下的方式进行操作:
<!--使用ref属性,为对应的Dom添加引用名称-->
<my-counter ref="counterRef"></h3>
<button @click="getRef">获取$refs引用</button>
<script>
export default{
methods:{
getRef(){
//通过this.$refs.引用的名称,可以获取到DOM元素的引用
console.log(this.$refs.counterRef)
//引用到组件的实例之后,就可以钓鱼组件上的methods方法
this.$refs.counterRef.add()
+ }
}
}
</script>
4.控制文本框和按钮的按需切换
通过布尔值**inputVisible
**控制组件中的文本框与按钮的按需切换。示例代码如下
<template>
<input type="text" v-if="inputVisible" />
<button v-else @click="showInput">展示input输入框</button>
</template>
<script>
export default{
data(){
return {
//控制文本语句和按钮的按需切换
inputVisible:false
}
},
methods:{
showInput(){
this.inputVisible=true
}
}
}
</script>
5.让文本框自动获得焦点
当文本框展示出来之后,如果希望它立即获得焦点,则可以为其添加 ref 引用,并调用原生 DOM 对象的
.focus() 方法即可。示例代码如下:
<input type="text" v-if="inputVisible" ref="ipt" />
<button v-else @click="showInput">展示input输入框</button>
<script>
export default {
methods:{
showInput(){
this.inputVisible=true
//获取文本框的DOM引用,并调用 .focus 使其自动获得焦点
this.refs.ipt.focus()
}
}
}
</script>
6.this.$nextTick(cb)方法
组件的 $nextTick(cb) 方法,会把 cb 回调推迟到下一个 DOM 更新周期之后执行。通俗的理解是:等组件的
DOM 更新完成之后,再执行 cb 回调函数。从而能保证 cb 回调函数可以操作到最新的 DOM 元素。
<input type="text" v-if="inputVisible" ref="ipt" />
<button v-else @click="showInput">展示input输入框</button>
<script>
export default {
methods:{
showInput(){
this.inputVisible=tru+e
//把对input文本框的操作,推迟到下次DOM更新之后,否则页面上根本不存在文本框元素
this.$nextTick(()=>{
this.refs.ipt.focus()
})
}
}
}
</script>
十一、动态组件
1.什么是动态组件
动态组件值指的是动态切换组件的显示与隐藏
2.如何实现动态组件的渲染
vue提供了内置的**<component>
**组件,专门用来实现动态组件的渲染,示例代码如下:
<template>
<!--2.通过is属性,动态指定要渲染的组件-->
<component :is="comName"></component>
<!--3.点击按钮,动态切换组件的名称-->
<button @click="comName='left'">显示Left组件</button>
<button @click="comName='Right'">显示Right组件</button>
</template>
<script>
export default{
data(){
//1.当前渲染的组件名称
return {comName:'Left'}
}
}
</script>
3.使用keep-alive保持状态
默认情况下,切换动态组件的时候,无法保持组件的状态
,这个时候我们可以使用Vue内置的**<keep-alive>
**组件保持动态组件的状态。示例代码如下:
<keep-alive>
<component :is="comName"></component>
</keep-alive>
keep-alive 对应的生命周期函数
当组件被缓存时,会自动触发组件的 **deactivated
**生命周期函数。
当组件被激活时,会自动触发组件的 **activated
**生命周期函数
export default{
created(){console.log('组件被创建了')},
destroyed(){console.log('组件被销毁了')},
activated(){console.log('Left组件被激活了')}
deactivated(){console.log('Left组件被缓存了')}
}
keep-alive 的 include
属性
include
属性用来指定:只有**名称匹配的组件会被缓存
**。多个组件名之间使用英文的逗号分隔:
<keep-alive include="MyLeft,MyRight">
<component :is="comName"></component>
</keep-alive>
十二、插槽
1.什么是插槽
插槽(slot)
是vue为组件的封装提供的能力,允许开发者在封装组件时,把不确定的、希望有用户指定的部分定义为插槽
可以把插槽认为是组件封装期间,为用户预留的**内容的占位符
**
2 体验插槽的基础用法
在封装组件时,可以通过 元素定义插槽,从而为用户预留内容占位符。
Left组件
<template>
<div class="left-container">
<h3>Left 组件</h3>
<hr />
<!-- 声明一个插槽区域 -->
<!-- vue 官方规定:每一个 slot 插槽,都要有一个 name 名称 -->
<!-- 如果省略了 slot 的 name 属性,则有一个默认名称叫做 default -->
<slot name="default">
<h6>这是 default 插槽的后备内容</h6>
</slot>
</div>
</template>
App组件
<div class="box" style="display: none;">
<!-- 渲染 Left 组件和 Right 组件 -->
<Left>
<template #default>
<p>这是在 Left 组件的内容区域,声明的 p 标签</p>
</template>
</Left>
</div>
1).没有预留插槽的内容会被丢弃
如果在封装组件时没有预留任何 插槽,则用户提供的任何自定义内容都会被丢弃。示例代码如下
left组件
<template>
<div class="left-container">
<h3>Left 组件</h3>
<hr />
<h6>这是 default 插槽的后备内容</h6>
</div>
</template>
App组件
<Left>
<!--自定义的内容会被直接丢弃-->
<p>这是在 Left 组件的内容区域,声明的 p 标签</p>
</Left>
2).后备内容
封装组件时,可以为预留的 插槽提供后备内容(默认内容)。如果组件的使用者没有为插槽提供任何内容,则后备内容会生效。
<template>
<div class="left-container">
<h3>Left 组件</h3>
<hr />
<slot>这一行是后备的内容</slot>
</div>
</template>
3. 具名插槽
如果在封装组件时需要预留多个插槽节点,则需要为每个 插槽指定具体的 name 名称。这种带有具体名称的插槽叫做**“具名插槽”**
Left组件
<template>
<header>
<!--我希望把页头放在这里-->
<slot name="header"></slot>
</header>
<main>
<!--我希望把主要内容放在这里-->
<slot></slot>
</main>
<footer>
<!--我希望把页脚放在这里-->
<slot name="footer"></slot>
</footer>
</template>
注意:没有指定 name 名称的插槽,会有隐含的名称叫做 “default”。
在向具名插槽提供内容的时候,我们可以在一个 元素上使用 v-slot 指令,并以 v-slot 的参数的形式提供其名称。
APP组件
<Left>
<template v-slot:header>
<h1>滕王阁序</h1>
</template>
<template v-slot:default>
<p>豫章故郡,洪都新府。</p>
</template>
<template v-slot:footer>
<p>
落款:王博
</p>
</template>
</Left>
1.具名插槽的简写形式
跟 v-on 和 v-bind 一样,v-slot 也有缩写,即把参数之前的所有内容 (v-slot:) 替换为字符 #。例如 v-slot:header,可以被重写为**#header
**:
<Left>
<template #header>
<h1>滕王阁序</h1>
</template>
<template #default>
<p>豫章故郡,洪都新府。</p>
</template>
<template #footer>
<p>
落款:王博
</p>
</template>
</Left>
4. 作用域插槽
在封装组件的过程中,可以为预留的 插槽绑定 props 数据,这种带有 props 数据的 叫做“作用域插槽”。示例代码如下:
<tbody>
<!--下面的slot是一个作用域插槽-->
<slot v-for="item in list" :user="item"></slot>
</tbody>
1).使用作用域插槽
可以使用 v-slot: 的形式,接收作用域插槽对外提供的数据。示例代码如下:
App组件
<Left>
<!--接收作用域插槽对外提供的数据-->
<template v-slot:default="scope">
<tr>
<!--使用作用域插槽的数据-->
<td>{{scope}}</td>
</tr>
</template>
</Left>
2).解构插槽 Prop
作用域插槽对外提供的数据对象,可以使用解构赋值简化数据的接收过程。示例代码如下:
<Left>
<!--接收作用域插槽对外提供的数据-->
<template #default="{user}">
<tr>
<!--使用作用域插槽的数据-->
<td>{{user.id}}</td>
<td>{{user.name}}</td>
<td>{{user.state}}</td>
</tr>
</template>
</Left>
十三、自定义指令
1. 什么是自定义指令
vue 官方提供了 v-text、v-for、v-model、v-if 等常用的指令。除此之外 vue 还允许开发者自定义指令
2. 自定义指令的分类
vue 中的自定义指令分为两类,分别是:
- 私有自定义指令
在每个 vue 组件中,可以在 directives 节点下声明私有自定义指令。示例代码如下:
directives:{
color:{
//为绑定到的HTML的元素设置红色文字
bind(el){
//形参中的el是绑定了次指令的’原生的DOM对象
el.style.color="red"
}
}
}
使用自定义指令
在使用自定义指令的时候,需要加上v-前缀,实例代码如下:
<!--声明自定义指令的时候,指令名字是color-->
<!--使用自定义指令的时候,需要加上v-指令的前缀-->
<h1 v-color>APP</h1>
为自定义指令动态绑定参数值
在 template 结构中使用自定义指令时,可以通过等号(=)的方式,为当前指令动态绑定参数值:
<!--在使用指令时,动态为当前指令绑定参数值color-->
<h1 v-color="color">APP</h1>
<script>
export default{
data(){
return{
color:'red' //定义color的颜色值
}
}
}
</script>
通过 binding 获取指令的参数值
在声明自定义指令时,可以通过形参中的第二个参数,来接收指令的参数值:
directives:{
color:{
//为绑定到的HTML的元素设置红色文字
bind(el,binding){
//形参中的el是绑定了次指令的’原生的DOM对象
el.style.color=binding.value
}
}
}
update 函数
bind 函数只调用 1 次:当指令第一次绑定到元素时调用,当 DOM 更新时 bind 函数不会被触发。 update 函数会在每次 DOM 更新时被调用。示例代码如下:
directives:{
color:{
//当前指令第一次被绑定到元素的时候被调用
bind(el,binding){
el.style.color=binding.value
},
//每次DOM更新时被调用
update(el,binding){
el.style.color=binding.value
}
}
}
函数简写
如果 insert 和update 函数中的逻辑完全相同,则对象格式的自定义指令可以简写成函数格式:
directives:{
color(el,binding){
el.style.color=binding.value
}
}
- 全局自定义指令
全局共享的自定义指令需要通过“Vue.directive()”进行声明,示例代码如下:
main.js
//参数1:字符串,表示全局自定义指令的名字
// 参数2:对象,用来接收指令的参数值
Vue.directive('color',function(el,binding){
el.style.color=binding.value
})
十四、路由
1,什么是路由
路由是一个比较广义和抽象的概念,路由的本质就是对应关系。在开发中路由一般分为两种:前端路由和后端路由
- 后端路由
根据不同的用户 URL 请求,返回不同的内容 ,本质上就是URL 请求地址与服务器资源之间的对应关系
- 前端路由
根据不同的用户事件,显示不同的页面内容 ,本质上是用户事件与事件处理函数之间的对应关系
2.什么是SPA(Single Page Application)
SPA(Single Page Application)单页面应用程序:整个网站只有一个页面,内容的变化通过Ajax局部更新实现、同时支持浏览器地址栏的前进和后退操作
SPA实现原理之一:基于URL地址的hash(hash的变化会导致浏览器记录访问历史的变化、但是hash的变化不会触发新的URL请求)
虽然前端路由是Ajax前端渲染不支持浏览器的前进后退操作但是前端渲染对比后端渲染能够提高性能 ,而实现SPA过程中,最核心的技术点就是前端路由
3。什么是前端路由
通俗易懂的概念:Hash 地址与组件之间的对应关系。
4.前端路由的工作方式
① 用户点击了页面上的路由链接
② 导致了 URL 地址栏中的 Hash 值发生了变化
③ 前端路由监听了到 Hash 地址的变化
④ 前端路由把当前 Hash 地址对应的组件渲染都浏览器中
前端路由
,指的是 Hash 地址与组件之间的对应关系!
5.实现简易的前端路由
步骤1:通过**<component>
**标签,结合 comName 动态渲染组件。示例代码如下:
<template>
<div class="app-container">
<h1>App 根组件</h1>
<a href="#/home">首页</a>
<a href="#/movie">电影</a>
<a href="#/about">关于</a>
<hr />
<!--通过is属性,指定要展示的组件名称-->
<component :is="comName"></component>
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
// 在动态组件的位置,要展示的组件的名字,值必须是字符串
comName: 'Home'
}
},
created() {
// 只要当前的 App 组件一被创建,就立即监听 window 对象的 onhashchange 事件
window.onhashchange = () => {
console.log('监听到了 hash 地址的变化', location.hash)
switch (location.hash) {
case '#/home':
this.comName = 'Home'
break
case '#/movie':
this.comName = 'Movie'
break
case '#/about':
this.comName = 'About'
break
}
}
},
}
</script>
6.什么是Vue-router
**vue-router
**是 vue.js 官方给出的路由解决方案。它只能结合 vue 项目进行使用,能够轻松的管理 SPA 项目 中组件的切换。
vue-router 的官方文档地址:https://router.vuejs.org/zh/
7.vue-router的安装和配置的步骤
- 在项目中安装vue-router
在vue2的项目中,安装vue-router的命令如下:
## 使用npm
npm i vue-router@3.5.3 -S
## 使用yarn
yarn add vue-router@3.5.3 -S
- 创建路由的模块
在**src
源代码目录下,新建router/index.js
**路由模块,并初识化代码如下
// 1.导入Vue和Vue-router的包
import Vue from 'vue'
import VueRouter from 'vue-router'
// 2.调用Vue.use() 函数,把VueRouter安装VUe插件
Vue.use(VueRouter)
// 3.创建路由的实例对象
const router =new VueRouter()
// 4.向外共享路由的实例对象
export default router
- 导入并挂载路由模块
在 src/main.js
入口文件中,导入并挂载路由模块。示例代码如下:
import Vue from 'vue'
import App from './App.vue'
// 导入路由模块
import router from '@/router'
new Vue({
render:h=>h(App)
//挂载路由模块
router:router
}).$mount('#app')
- 声明路由链接和占位符
在 src/App.vue 组件中,使用 vue-router 提供的 <router-link>
和 <router-view>
声明路由链接和占位符:
<template>
<div class="app-container">
<h1>App2 组件</h1>
<!-- 例如: -->
<!-- /movie/2?name=zs&age=20 是 fullPath 的值 -->
<!-- 定义路由链接 -->
<router-link to="/movie/1">洛基</router-link>
<router-link to="/movie/2?name=zs&age=20">雷神</router-link>
<router-link to="/movie/3">复联</router-link>
<router-link to="/about">关于</router-link>
<hr />
<!-- 只要在项目中安装和配置了 vue-router,就可以使用 router-view 这个组件了 -->
<!-- 它的作用很单纯:占位符 -->
<router-view></router-view>
</div>
</template>
- 声明路由的匹配规则
在 src/router/index.js 路由模块中,通过 routes 数组声明路由的匹配规则。示例代码如下:
// 1.导入Vue和Vue-router的包
import Vue from 'vue'
import VueRouter from 'vue-router'
// 导入需要的组件
import Home from '@/components/Home.vue'
import Movie from '@/components/Movie.vue'
import About from '@/components/About.vue'
// 2.调用Vue.use() 函数,把VueRouter安装VUe插件
Vue.use(VueRouter)
// 3.创建路由的实例对象
const router =new VueRouter({
//在routes数组中,声明路由的匹配规则
routes:[
//path表示要匹配的哈希<hash>地址,
//component表示要展示的路由组件
{path:'/home',component:Home},
{path:'/movie',component:Movie},
{path:'/about',component:About}
]
})
// 4.向外共享路由的实例对象
export default router
8.vue-router 的常见用法
- 路由重定向
路由重定向指的是:用户在访问地址 A 的时候,强制用户跳转到地址 C ,从而展示特定的组件页面。 通过路由规则的 redirect 属性,指定一个新的路由地址,可以很方便地设置路由的重定向:
// 1.导入Vue和Vue-router的包
import Vue from 'vue'
import VueRouter from 'vue-router'
// 导入需要的组件
import Home from '@/components/Home.vue'
import Movie from '@/components/Movie.vue'
import About from '@/components/About.vue'
// 2.调用Vue.use() 函数,把VueRouter安装VUe插件
Vue.use(VueRouter)
// 3.创建路由的实例对象
const router =new VueRouter({
//在routes数组中,声明路由的匹配规则
routes:[
//path表示要匹配的哈希<hash>地址,
//component表示要展示的路由组件,
// 当用户访问/的时候,通过redirect属性跳转到/home对应的路由规则
{path:'/',redirect:'/home'},
{path:'/home',component:Home},
{path:'/movie',component:Movie},
{path:'/about',component:About}
]
})
// 4.向外共享路由的实例对象
export default router
- 嵌套路由
通过路由实现组件的嵌套展示,叫做嵌套路由。
- 声明**
子路由链接
和子路由占位符
**
在 About.vue 组件中,声明 tab1 和 tab2 的子路由链接以及子路由占位符。示例代码如下:
<template>
<div class="about-container">
<h3>About 组件</h3>
<!-- 子级路由链接 -->
<router-link to="/about">tab1</router-link>
<router-link to="/about/tab2">tab2</router-link>
<hr />
<!-- 子级路由占位符 -->
<router-view></router-view>
</div>
</template>
通过 children 属性声明子路由规则
在 src/router/index.js 路由模块中,导入需要的组件,并使用 children 属性声明子路由规则:
// 1.导入Vue和Vue-router的包
import Vue from 'vue'
import VueRouter from 'vue-router'
// 导入需要的组件
import Tab1 from '@/components/tabs/Tab1.vue'
import Tab2 from '@/components/tabs/Tab2.vue'
// 2.调用Vue.use() 函数,把VueRouter安装VUe插件
Vue.use(VueRouter)
// 3.创建路由的实例对象
const router =new VueRouter({
//在routes数组中,声明路由的匹配规则
routes:[
//path表示要匹配的哈希<hash>地址,
//component表示要展示的路由组件,
// 当用户访问/的时候,通过redirect属性跳转到/home对应的路由规则
{path:'/',redirect:'/home'},
{path:'/home',component:Home},
{path:'/movie',component:Movie},
{
path: '/about',
component: About,
// redirect: '/about/tab1',
children: [
// 子路由规则
// 默认子路由:如果 children 数组中,某个路由规则的 path 值为空字符串,则这条路由规则,叫做“默认子路由”
{ path: '', component: Tab1 },
{ path: 'tab2', component: Tab2 }
]
}
]
})
// 4.向外共享路由的实例对象
export default router
- 动态路由的概念
动态路由指的是:把 Hash 地址中可变的部分定义为参数项,从而提高路由规则的复用性。 在 vue-router 中使用英文的冒号(:)来定义路由的参数项。示例代码如下:
// 可以为路由规则开启 props 传参,从而方便的拿到动态参数的值
{ path: '/movie/:mid', component: Movie, props: true },
4.2 $route.params 参数对象
在动态路由渲染出来的组件中,可以使用 this.$route.params 对象访问到动态匹配的参数值
<template>
<div class="movie-container">
<!-- this.$route 是路由的“参数对象” -->
<!-- this.$router 是路由的“导航对象” -->
<h3>Movie 组件 --- {{ $route.params.mid }} --- {{ mid }}</h3>
</div>
</template>
4.3. 使用 props 接收路由参数
为了简化路由参数的获取形式,vue-router 允许在路由规则中开启 props 传参。示例代码如下:
<template>
<div class="movie-container">
<!-- this.$route 是路由的“参数对象” -->
<!-- this.$router 是路由的“导航对象” -->
<h3>Movie 组件 --- {{ $route.params.mid }} --- {{ mid }}</h3>
<button @click="showThis">打印 this</button>
<button @click="goback">后退</button>
<!-- 在行内使用编程式导航跳转的时候,this 必须要省略,否则会报错! -->
<button @click="$router.back()">back 后退</button>
<button @click="$router.forward()">forward 前进</button>
</div>
</template>
<script>
export default {
name: 'Movie',
// 接收 props 数据
props: ['mid'],
methods: {
showThis() {
console.log(this)
},
goback() {
// go(-1) 表示后退一层
// 如果后退的层数超过上限,则原地不动
this.$router.go(-1)
}
}
}
</script>
-
声明式导航 & 编程式导航
- 在浏览器中,点击链接实现导航的方式,叫做声明式导航。例如:普通网页中点击 ****链接、vue 项目中点击 ****都属于声明式导航
- 在浏览器中,调用 API 方法实现导航的方式,叫做编程式导航。例如: 普通网页中调用
location.href
跳转到新页面的方式,属于编程式导航
-
vue-router 中的编程式导航 API
- this.$router.push(‘hash 地址’)
调用 this.$router.push() 方法,可以跳转到指定的 hash 地址,从而展示对应的组件页面。示例代码如下:
<template>
<div class="home-container">
<h3>Home 组件</h3>
<hr />
<button @click="gotoLk">通过 push 跳转到“洛基”页面</button>
<button @click="gotoLk2">通过 replace 跳转到“洛基”页面</button>
<router-link to="/main">访问后台主页</router-link>
</div>
</template>
<script>
export default {
name: 'Home',
methods: {
gotoLk() {
// 通过编程式导航 API,导航跳转到指定的页面
this.$router.push('/movie/1')
}
}
}
</script>
- this.$router.replace()
调用 this.$router.replace() 方法,可以跳转到指定的 hash 地址,从而展示对应的组件页面。
push 和 replace 的区别:
⚫ push 会增加一条历史记录
⚫replace 不会增加历史记录,而是替换掉当前的历史记录
- this.$router.go()
调用 this.$router.go() 方法,可以在浏览历史中前进和后退。示例代码如下:
<template>
<div class="movie-container">
<button @click="goback">后退</button>
</div>
</template>
<script>
export default {
name: 'Movie',
// 接收 props 数据
props: ['mid'],
methods: {
showThis() {
console.log(this)
},
goback() {
// go(-1) 表示后退一层
// 如果后退的层数超过上限,则原地不动
this.$router.go(-1)
}
}
}
</script>
$router.go 的简化用法:
在实际开发中,一般只会前进和后退一层页面。因此 vue-router 提供了如下两个便捷方法:
① $router.back():在历史记录中,后退到上一个页面
② $router.forward():在历史记录中,前进到下一个页面
- 导航守卫
导航守卫可以控制路由的访问权限。示意图如下:
全局前置守卫
每次发生路由的导航跳转时,都会触发全局前置守卫。因此,在全局前置守卫中,程序员可以对每个路由进行 访问权限的控制:
// 为 router 实例对象,声明全局前置导航守卫
// 只要发生了路由的跳转,必然会触发 beforeEach 指定的 function 回调函数
router.beforeEach(function(to, from, next) {
// to 表示将要访问的路由的信息对象
// from 表示将要离开的路由的信息对象
// next() 函数表示放行的意思
// 分析:
// 1. 要拿到用户将要访问的 hash 地址
// 2. 判断 hash 地址是否等于 /main。
// 2.1 如果等于 /main,证明需要登录之后,才能访问成功
// 2.2 如果不等于 /main,则不需要登录,直接放行 next()
// 3. 如果访问的地址是 /main。则需要读取 localStorage 中的 token 值
// 3.1 如果有 token,则放行
// 3.2 如果没有 token,则强制跳转到 /login 登录页
if (to.path === '/main') {
// 要访问后台主页,需要判断是否有 token
const token = localStorage.getItem('token')
if (token) {
next()
} else {
// 没有登录,强制跳转到登录页
next('/login')
}
} else {
next()
}
})
守卫方法的 3 个形参
全局前置守卫的回调函数中接收 3 个形参,格式为:
const router = new VueRouter({...})
//全局前置前置导航
router.beforeEach((to,from,next)=>{
//to是将要访问的路由的信息对象
//from 是将要离开的路由的信息对象
//next是一个函数,调用next()表示放行,允许这次路由导航
})
next 函数的 3 种调用方式
参考示意图,分析 next 函数的 3 种调用方式最终导致的结果:
当前用户拥有后台主页的访问权限,直接放行:next()
当前用户没有后台主页的访问权限,强制其跳转到登录页面:next(’/login’)
当前用户没有后台主页的访问权限,不允许跳转到后台主页:next(false)
6.4 控制后台主页的访问权限
// 3. 如果访问的地址是 /main。则需要读取 localStorage 中的 token 值
// 3.1 如果有 token,则放行
// 3.2 如果没有 token,则强制跳转到 /login 登录页
if (to.path === '/main') {
// 要访问后台主页,需要判断是否有 token
const token = localStorage.getItem('token')
if (token) {
next()
} else {
// 没有登录,强制跳转到登录页
next('/login')
}
} else {
next()
}