一. 什么是VUE:
- 什么是:
(1). 第三方开发的 —— 下载
(2). 基于MVVM设计模式的 ?
(3). 渐进式的 —— 可以逐步在项目中使用vue的部分继承。而不要求必须把整个项目都改为vue。
(4). 纯前端js —— 只要有浏览器就可以运行,和后端nodejs无关。
(5). 框架 —— 自动化,避免大量重复劳动 - 为什么: 简化开发,避免重复劳动!
- 何时: 今后,凡是以数据操作(增删改查)为主的项目,都适合用vue等框架来实现。
比如: 饿了么,美团,滴滴,京东,淘宝,头条,知乎,微博… - 问题:
(1). 兼容性问题
(2). 难以接受
二. 如何使用:
- 下载: cn.vuejs.org
(1). 下载vue.js文件,引入HTML中使用:
a. 开发版: 未压缩版,包含完整的注释,代码格式和见名知义的变量名。而且,还包含极其友好的错误提示信息。
适合学习和开发之用
b. 生产版: 压缩版,去掉了所有注释,代码格式,极简化了变量名。而且,去掉了友好的错误提示信息。
适合生产环境上线使用。
(2). 安装脚手架 - 我的第一个vue程序:
1_first_jq.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="js/jquery-1.11.3.js"></script>
</head>
<body>
<button id="btnMinus">-</button>
<span>0</span>
<button id="btnAdd">+</button>
<script>
//先实现+
//DOM 4步
//1. 查找触发事件的元素
$("#btnAdd")
//2. 绑定事件处理函数
.click(function(){
//3. 查找要修改的
var $span=$(this).prev();
//4. 修改元素
//4.1 先取出span中现在的数量,转为整数
var n=parseInt($span.html());
//4.2 将n+1
n++
//4.3 将新的n再放回去:
$span.html(n);
})
//再实现-
//DOM 4步
//1. 查找触发事件的元素
$("#btnMinus")
//2. 绑定事件处理函数
.click(function(){
//3. 查找要修改的
var $span=$(this).next();
//4. 修改元素
//4.1 先取出span中现在的数量,转为整数
var n=parseInt($span.html());
//4.2 如果n>0,才能n-1
if(n>0){
n--
}
//4.3 将新的n再放回去:
$span.html(n);
})
</script>
</body>
</html>
运行结果:
2_first_vue.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!--先引入vue.js-->
<script src="js/vue.js"></script>
</head>
<body>
<!--VUE 3步-->
<!--1. 先做界面: 3个要求
1.1 统一的要求: 界面中所有元素,都必须包裹在一个唯一的父元素下,习惯上<div id="app"></div>
1.2 找到界面中将来可能随程序自动变化的位置,用专门的语法:{{变量名}}来标记/占位
本例中:?
1.3 找到界面中将来可能触发事件的元素,用专门的语法: @事件名="事件处理函数名" 来标记
本例中: ?-->
<div id="app">
<button @click="minus">-</button>
<span>{{n}}</span>
<button @click="add">+</button>
</div>
<script>
//2. 创建一个new Vue()对象,来监控div所包含的区域。
var vm=new Vue({
//vue对象中,必须用el属性,告诉new Vue()要监控的区域是哪里: (el其实是element的缩写)
// id选择器
el:"#app",
//3. 定义模型对象,来保存界面中所需的所有变量和事件处理函数
//什么是模型对象: 就是专门替界面保存变量和事件处理函数的特殊的对象
//3.1 先创建一个data:{}来保存界面中所需的所有变量和初始值
//本例中: 因为界面上只需要一个变量n,所以data中才需要1个变量n
data:{
n:0
},
//3.2 再创建一个methods:{}来保存界面中所需的所有事件处理函数
//本例中: 因为界面中需要2个事件处理函数,所以methods中应该定义两个函数
methods:{
add(){
this.n++;
},
minus(){
if(this.n>0){
this.n--;
}
}
}
//强调:
//3.2.1 methods中的事件处理函数中,如果要操作data中的变量,必须加this.
//3.2.2 methods中的事件处理函数中,根本不用考虑如何从界面取值,也不用考虑如何将新值放回界面,只需要专门考虑如何把data中的变量值修改正确即可!
//new Vue()会自动保持界面中变量n和data中变量n同步:
//开局时,data中n是几,new Vue()就把n的值送到页面上,对应位置显示给人看
//当methods中修改了n的值,new Vue()会自动把n的新值自动更新到界面中n所在的位置给人看
});
console.log(vm);
</script>
</body>
</html>
运行结果:
三. MVVM:
- 旧的前端程序划分为三部分:
(1). HTML: 专门定义网页内容
(2). CSS: 专门定义网页内容的样式
(3). JS: 专门为网页添加交互行为 - 问题: HTML和CSS功能太弱,甚至不支持程序该有基本功能: 变量,运算,分支、循环。所以,哪怕一点儿修改,都要麻烦js来操作。导致js的任务量非常繁重,且存在大量重复劳动。
- 解决: MVVM设计模式,是对前端三大部分的重新划分。
(1). 界面(View):
a. 包括HTML+CSS
b. 增强版: 支持程序的基本功能: 变量,运算,分支和循环.
(2). 模型对象(Model):
a. 什么是: 专门替界面保存所属的变量和函数的特殊对象
b. 今后,只要界面上需要用到哪些变量和函数时,都要保存在模型对象中
(3). 视图模型(ViewModel)
a. 什么是: 专门自动将模型对象中的变量和函数,自动传递到界面上指定位置的特殊对象
b. 何时: 今后,只要希望有人自动将模型对象中的变量和函数自动运送到界面上,都要定义视图模型。 - Vue框架如何实现MVVM设计模式的: Vue的绑定原理
(1). 创建new Vue()对象, 2件事:
a. 隐藏data中的原变量,自动为data中原变量创建访问器属性。从此,在new Vue中使用的任何变量,其实都只能是访问器属性。
b. 引入methods并打散methods为多个函数。
1). 原methods中的函数,不再隶属于methods,而是直接隶属于new Vue()对象了
2). 所以,methods中的函数打散后与访问器属性平级,所以,methods中的方法,想访问访问器属性变量,必须加this.
(2). new Vue()中el:"#app"在指引new Vue()对象去创建虚拟DOM树
a. 扫描el属性:"#app"所指的页面区域的真实DOM树,只找出可能发生变化的元素,保存进虚拟DOM树中
b. 今后,只要new Vue()中修改了变量,其实就是修改了访问器属性。
1). 访问器属性就会向虚拟DOM树发出通知
2). 虚拟DOM树扫描内部所有可能发生变化的元素,找出受本次变量修改影响的个别元素。
3). 虚拟DOM树利用内部已经封装好的DOM操作,只修改页面上受本次影响的元素。不受本次影响的元素,不会改变。
(3). 总结: Vue的绑定原理=访问器属性+虚拟DOM树 - 虚拟DOM树的优点:
(1). 小 —— 只包含受影响的元素
(2). 遍历快
(3). 更新效率高 —— 只更新受影响的元素,不受影响的元素保持不变。
(4). 避免重复编码 —— 已经封装了DOM增删改查操作,可自动更新页面内容。
四. 绑定语法: 学名: 插值语法 Interpolation
- 什么是: 专门在HTML语言中标记可能发生变化的元素内容的特殊语法
- 何时: 今后,只要一个元素的内容,在程序执行过程中,可能会被改变,都要用绑定语法来标记
- 如何: <元素>其它文本{{变量名}}其它文本</元素>
- 结果:
(1). 首次加载页面时,new Vue()会自动去data中查找同名的变量,并将变量值,替换{{}}所在的位置
(2). 今后只要在new Vue()中修改了{{}}中的变量后,new Vue()都会自动将变量的新值重新替换{{}}的位置。 - {{}}内部: 和模板字符串的${}中规则一样
(1). 可以放: 有返回值的语法正确的js表达式
比如: 变量, 算术计算, 三目, 访问数组元素, 创建对象, 调用方法。
(2). 不可以放: 分支和循环以及没有返回值的js表达式 - 示例: 使用绑定语法展示各种数据:
3_{{}}.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="js/vue.js"></script>
</head>
<body>
<div id="app">
<h3>Welcome {{uname}}</h3>
<h3>性别:{{sex==1?"男":"女"}}</h3>
<h3>小计:¥{{(price*count).toFixed(2)}}</h3>
<h3>下单时间: {{new Date(orderTime).toLocaleString()}}</h3>
<h3>今天星期{{week[day]}}</h3>
</div>
<script>
new Vue({
el:"#app",
data:{
uname:"dingding",
sex:1,
price:12.5,
count:5,
orderTime:1614158191101,
week:["日","一","二","三","四","五","六"],
day:new Date().getDay()
}
})
</script>
</body>
</html>
运行结果:
五. 指令(directive):
- 什么是: 为HTML元素添加新功能的特殊的自定义HTML属性
- 为什么: 为了给增强版的HTML提供以前不支持的分支循环等新功能
- vue中包括: 13种:
(1). v-bind:
a. 什么是: 专门用来标记属性值变化的特殊指令
b. 如何:
1). 标准: <元素 v-bind:属性名=“变量或js表达式”>
html地盘 | js的地盘 |
2). 简写: <元素 :属性名=“变量或js表达式”>
c. 原理: new Vue()只要扫描到":",就会先执行=右侧"“中的js变量或表达式。然后用变量值或js表达式的运行结果,作为当前属性的属性值!
d. 强调:
1). 一旦属性上加了:,则不要再加{{}}了!”"就起到了{{}}的作用。
2). ""之内变成了js的语法天下!必须遵守js的语法!
e. 示例: 根据pm25数值,显示不同的图片
4_v-bind.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>根据程序中PM25的数值显示不同的表情</title>
<script src="js/vue.js"></script>
</head>
<body>
<div id="app">
<!--哪里可能随程序自动改变?-->
<img :src="pm25<100?'img/1.png':pm25<200?'img/2.png':pm25<300?'img/3.png':'img/4.png'">
<h3>{{pm25<100?'img/1.png':pm25<200?'img/2.png':pm25<300?'img/3.png':'img/4.png'}}</h3>
</div>
<script>
var vm=new Vue({
el:"#app",
data:{
//程序中只保存pm2.5的数值,不保存图片路径
pm25:160
}
})
//运行后,F12打开控制台,console,输入vm.pm25=数值,观察页面上图片的变化.
</script>
</body>
</html>
运行结果:
(2). v-show:
a. 什么是: 专门根据条件控制一个元素的显示隐藏
b. 如何: <元素 v-show=“变量或js表达式”>
c. 原理: new Vue()只要扫描到v-show,就会先执行=右边""中的变量或js表达式:
1). 如果=右边的变量或js表达式结果为true,则当前元素正常显示
2). 如果=右边的变量或js表达式结果为false,则当前元素隐藏(new Vue()自动将v-show替换为display:none)!
d. 示例: 点按钮弹出框
5_v-show.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="js/vue.js"></script>
<style>
#pop{
position:fixed;
top:50%;
left:50%;
width:300px;
height:100px;
margin-left:-150px;
margin-top:-50px;
background-color:lightBlue
}
#pop>a{
float:right;
margin-right:20px;
text-decoration:none;
}
</style>
</head>
<body>
<!--VUE 3步
1. 做界面:
1.1 唯一父元素
1.2 找可能发生变化的元素
本例中:div的显示隐藏状态有可能变化
1.3 找触发事件的元素
本例中:
button触发事件,显示对话框
a触发事件,隐藏对话框
-->
<div id="app">
<button @click="show">click me</button>
<div v-show="visible" id="pop">
<a href="javascript:;" @click="hide">×</a>
</div>
</div>
<script>
//2. 创建new Vue()对象,监控id为app的区域
new Vue({
el:"#app",
//3. 创建模型对象:
//3.1 创建data对象
//本例中: 只用到一个变量visible,所以
data:{
visible:false, //专门用来保存弹出框的显示隐藏状态。开局,因为弹出框隐藏,所以变量值为false。
},
//3.2 创建methods对象
//本例中: 因为界面上需要2个事件处理函数,所以,methods中也应该保存2个事件处理函数
methods:{
show(){
this.visible=true;
},
hide(){
this.visible=false;
}
}
})
</script>
</body>
</html>
运行结果:
(3). v-if和v-else:
a. 什么是: 专门根据条件在两个元素之间二选一显示隐藏
b. 如何:
<元素1 v-if=“变量或js表达式”>
<元素2 v-else>
强调: v-if和v-else两个元素之间必须紧挨着写,不能插入任何其他元素.
c. 原理: new Vue()扫描到v-if时,总是先执行=右边""中的变量和js表达式:
1). 如果v-if=右边的变量或js表达式返回true,则显示v-if所在的元素,而删除v-else所在元素
2). 如果v-if=右边的变量或js表达式返回false,则删除v-if所在的元素,而保存v-else所在的元素
d. 问题:为什么已经删除的元素,后来还能回到页面上
原理: 每次删除的指示真实DOM树中的元素对象。而虚拟DOM树中始终同时保存着v-if和v-else两个元素对象。每次变量发生变化时,并不是直接扫描真实DOM树,而是扫描虚拟DOM树中两个元素对象,决定这次派谁上场!
e. v-if和v-show的差别:
1). v-show: 用display:none隐藏元素
2). v-if: 用删除元素方式来达到隐藏的目的
f. 示例: 切换登录状态:
6_v-if_v-else.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="js/vue.js"></script>
</head>
<body>
<!--VUE 3步
1. 做界面
1.1 唯一父元素包裹
1.2 找可能发生变化的元素
本例中: 两个div根据条件不同二选一显示
1.3 找触发事件的元素
本例中:
点击登录的a元素时,切换为已登录状态
点击注销的a元素时,切换为未登录状态-->
<div id="app">
<!--已登录时显示-->
<!-- 记录当前用户是否登录-->
<div v-if="isLogin">
<h3>Welcome dingding <a href="javascript:;" @click="logout">注销</a></h3>
</div>
<!--未登录时显示-->
<div v-else>
<a href="javascript:;" @click="login">登录</a> |
<a href="javascript:;">注册</a>
</div>
</div>
<script>
//2. 创建new Vue()
new Vue({
el:"#app",
//3. 创建模型对象
//3.1 创建data对象
//本例中:因为界面上需要1个变量,所以
data:{
isLogin:false,//记录用户是否登录。开局,用户暂时未登录,所以初始值为false
},
//3.2 创建methods对象
//本例中:因为界面中需要2个函数,所以
methods:{
login(){
//让用户变成已登录状态
this.isLogin=true;
},
logout(){
//让用户变回未登录状态
this.isLogin=false;
}
}
})
</script>
</body>
</html>
运行结果::
(4). v-else-if:
a. 什么是: 和v-if v-else配合来控制多个元素多选一显示的特殊指令
b. 如何:
<元素1 v-if=“条件1”>
<元素2 v-else-if=“条件2”>
… …
<元素n v-else>
c. 原理: new Vue()扫描到v-if时,先执行=右边的条件1。
1). 如果条件1为true,则显示v-if所在的元素,删除之后所有v-else-if和v-else
2). 如果条件1为false,则先删除v-if所在的元素,依次判断v-else-if后的条件。只要有任何一个v-else-if条件满足,则只保留这一个v-else-if所在的元素,删除其余元素
3). 如果所有v-if 和v-else-if的条件都不满足,则删除所有v-if和v-else-if所在元素,只保留v-else
d. 示例: 根据pm25值不同,显示不同表情
7_v-else-if.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="js/vue.js"></script>
</head>
<body>
<div id="app">
<img v-if="pm25<100" src="img/1.png" alt="">
<img v-else-if="pm25<200" src="img/2.png" alt="">
<img v-else-if="pm25<300" src="img/3.png" alt="">
<img v-else src="img/4.png" alt="">
</div>
<script>
var vm=new Vue({
el:"#app",
data:{
pm25:160
}
})
</script>
</body>
</html>
运行结果:
(5). v-for:
a. 什么是: 专门根据一个数组的内容反复生成多个相同结构的HTML元素的特殊指令
b. 何时: 今后,只要希望根据一个数组中的元素内容,来生成相应个数的HTML元素,都要用v-for。
c. 如何:
<要反复生成的元素 v-for="(元素值, 下标i) of 数组">
强调: of前的()中第一个变量必须接住值,第二个变量必须接住下标/属性名。变量名可以随便改,但是顺序改不了。
d. 原理: new Vue()只要扫描到v-for,就会自动遍历of后的数组中每个元素。每遍历一个元素,就自动返回元素的内容和下标,保存到of前的两个变量中。并且,每遍历一个元素, v-for都自动创建一个当前HTML元素的副本。遍历了数组中的几个元素,最终就会反复创建几个相同结构的HTML元素副本。
e. v-for还能遍历对象中每个属性:
1). 其实vue中的v-for统一了js中for of和for in的功能
2). 其实v-for="(元素值, 下标i) in 数组/对象"
3). 其实写in和写of完全一样!都是既能遍历索引数组,又能遍历对象中的属性。
f. VUE2的两个著名的坑:
1). 如果使用"数组[下标]=新值"方式来修改数组中某个位置的元素,则即使数组内容可以修改成功,那么页面也不会跟着自动变化
原因: vue中并没有给数组中的数字下标添加任何访问器属性,所以vue中所有数字下标,都是不受监控的!
临时解决: 所有对数组的修改,不要用下标来做。用数组类型提供的那些API函数来实现。
比如: 想修改数组中1位置的元素值为新值:
错误: 数组[1]=新值
正确: 数组.splice(1,1,新值)
从1位置删除1个元素
再将新值插入到1位置——完成替换
将来解决: 升级到VUE3,自动解决了.
2). 在运行时,为对象添加新属性,则新属性不会自动更新界面。
原因: VUE2的new Vue()只能对开局时对象中现有的属性自动添加访问器属性。而,将来在运行时,由程序动态添加的属性,都无法实时自动获得访问器属性。所以,又会出现不受监控的属性
解决: 只能升级VUE3
g. v-for的问题:
1). v-for很蠢,即使我们在程序中只修改了一个元素值,因为v-for无法区分页面上的HTML元素,所以,只能删除整个列表,重建整个列表。——效率低
2). 根本原因: v-for反复生成的多个HTML元素副本,没有差别!
3). 解决: vue规定,今后只要使用v-for,都必须为元素同时绑定:key属性,且属性值应该是一个不重复的下标或属性名。
4). 如何:
<元素 v-for="(元素值, 下标i) of 数组" :key=“下标i”>
5). 原理:
i. 今后,v-for每创建一个HTML元素副本,都会绑定一个key属性,属性值为数组中一个不重复的下标
ii. 今后,当更新数组中某一个元素时,v-for就可根据HTML元素上绑定的唯一的key属性值,只找到对应的一个HTML元素,修改其内容即可。不会更新列表——效率高!
6). 总结: v-for为什么必须带:key
为每个元素添加唯一标识,避免更新一个元素时重建整个列表。
h. 示例: 使用v-for遍历索引数组和对象
8_v-for.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="js/vue.js"></script>
</head>
<body>
<div id="app">
<h3>遍历数组中每个元素</h3>
<ul>
<li v-for="(str,i) of arr" :key="i">
{{i+1}} - {{str}}
</li>
</ul>
<h3>遍历对象中每个属性</h3>
<ul>
<li v-for="(属性值, 属性名) of lilei" :key="属性名">{{属性名}} - {{属性值}}</li>
</ul>
</div>
<script>
var vm=new Vue({
el:"#app",
data:{
arr:["亮亮","楠楠","东东"],
lilei:{
sname:"Li Lei",
sage:11,
class:"初一2班"
}
}
})
//1. vm.arr[1]="涛涛" //修改不成功
//2. vm.arr.splice(1,1,"涛涛") //成功
//3. vm.lilei.money=100 //没成功
</script>
</body>
</html>
运行结果:
i. 其实v-for也可以根据一个整数反复生成指定个数的HTML元素.
1). <元素 v-for=“i of 整数” :key=“i”>
2). 原理: v-for会自动从1开始,连续数数,数到给定的整数结束。每数一个数,就自动创建当前HTML元素的一个副本。
3). 示例: 生成分页按钮
9_v-for_pages.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="js/vue.js"></script>
<style>
ul{
list-style: none;
}
ul>li{
float:left;
width:40px;
height:40px;
text-align:center;
line-height:40px;
border:1px solid #555;
border-radius: 5px;
margin-left:5px;
}
ul>li:hover{
cursor:pointer;
background-color:lightskyblue;
color:#fff;
border:0;
}
</style>
</head>
<body>
<div id="app">
<ul>
<li v-for="i of pageCount" :key="i">{{i}}</li>
</ul>
</div>
<script>
var vm=new Vue({
el:"#app",
data:{
pageCount:10 //共6页
}
})
</script>
</body>
</html>
运行结果:
(6).v-on:
(1). 什么是: 专门为元素绑定事件处理函数的指令
(2). 如何: 2步:
a. 第一步:
1). 标准: <元素 v-on:事件名=“事件处理函数(实参,…)”>
2). 简写:
i. @事件名=“事件处理函数(实参,…)”
ii. 如果不需要传实参值,则可以省略()
@事件名=“事件处理函数名”
b. 第二步: 页面中所需的所有事件处理函数必须在methods对象中集中定义。
methods:{
事件处理函数(形参变量, …){ … }
}
(3). 如何获得事件对象:
a. 只获得事件对象,不传其它自定义实参值:
<元素 @事件名=“事件处理函数”> //不要加()
methods:{
//当事件发生时
//浏览器自动创建event对象
// ↓
事件处理函数( e ){
}
}
b. 如何既获得事件对象,又传自定义实参
//浏览器自动创建的 event对象
// ↓
<元素 @事件名="处理函数($event, 自定义实参,...)"
//今后,vue中凡是$开头的都是有特殊意义的关键字,不能改名!
//$event 专门用于在vue事件绑定时获得事件对象。
//今后,每当触发事件时,$event都会抢先获得浏览器自动创建的event对象,保存起来备用。
methods:{
事件处理函数(e, 自定义形参,...){
e->$event对象->浏览器的event对象
自定义形参->自定义实参
}
}
(3). 示例: 点谁,谁喊疼:
5_@.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="js/vue.js"></script>
<style>
img{ width:250px; }
</style>
</head>
<body>
<!--点谁的哪个部位,就喊谁的哪个部位疼!-->
<div id="app">
<img src="img/liang.jpg" @click="say($event,'liang')">
<img src="img/tao.jpg" @click="say($event,'tao')">
</div>
<script>
new Vue({
el:"#app",
methods:{
say(e, name){
if(e.offsetY<120){
console.log(`${name} 头疼!`)
}else if(e.offsetY<240){
console.log(`${name} 胸疼!`)
}else{
console.log(`${name} 肚子疼!`)
}
}
}
})
</script>
</body>
</html>
6_@_e.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="js/vue.js"></script>
<style>
img{ width:250px; }
</style>
</head>
<body>
<!--点哪个部位,就喊哪个部位疼!-->
<div id="app">
<img src="img/liang.jpg" @click="say">
<img src="img/tao.jpg" @click="say">
</div>
<script>
new Vue({
el:"#app",
methods:{
say(e){
if(e.offsetY<120){
console.log(`头疼!`)
}else if(e.offsetY<240){
console.log(`胸疼!`)
}else{
console.log(`肚子疼!`)
}
}
}
})
</script>
</body>
</html>
7_@_$event.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="js/vue.js"></script>
<style>
img{ width:250px; }
</style>
</head>
<body>
<!--点谁的哪个部位,就喊谁的哪个部位疼!-->
<div id="app">
<img src="img/liang.jpg" @click="say($event,'liang')">
<img src="img/tao.jpg" @click="say($event,'tao')">
</div>
<script>
new Vue({
el:"#app",
methods:{
say(e, name){
if(e.offsetY<120){
console.log(`${name} 头疼!`)
}else if(e.offsetY<240){
console.log(`${name} 胸疼!`)
}else{
console.log(`${name} 肚子疼!`)
}
}
}
})
</script>
</body>
</html>
(7).v-html:
(1). 问题: 如果要绑定的字符串内容中包含内嵌的HTML标签或特殊符号,如果使用{{}}绑定,则不会交给浏览器解析,而是原样显示HTML代码——不是我们想要的
(2). 原因: {{}}底层本质是textContent。DOM中的textContent特点就是如果修改内容时,内容中包含html代码,则不交给浏览器解析,而是直接显示。
(3). 解决:
a. DOM中: 只要要修改的新内容中包含HTML片段,都要用innerHTML来修改。因为innerHTML会先把HTML片段交给浏览器解析。然后将解析后的结果显示给人看。
b. vue中: v-html指令等效于innerHTML
(4). 今后,只要要绑定的内容中包含HTML片段,都要用v-html指令来绑定。不要用{{}}
(5). 如何: <元素 v-html=“变量”> </元素>
(6). 原理: v-html会先将变量中的字符串内容交给浏览器解析。然后将解析后的内容覆盖掉当前元素开始标签到结束标签之间的旧内容。
(7). 示例: 使用v-html绑定HTML内容
8_v-html.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="js/vue.js"></script>
</head>
<body>
<div id="app">
<h3>{{msg}}</h3>
<h3 v-html="msg"></h3>
</div>
<script>
new Vue({
el:"#app",
data:{
msg:`来自<a href="#"><<新华社>></a>的消息`
}
})
</script>
</body>
</html>
运行结果:
(7). v-cloak和v-text:
(1). 问题: 当网络状况不好时,new Vue()对象所在的js文件下载可能会延迟加载。导致用户短暂看到页面上的{{}}
(2). 解决: 2种:
a. v-cloak:
1). 什么是: 在new Vue()加载之前,暂时隐藏元素的特殊指令
2). 如何: 2步:
i. 必须手工在样式表中添加:
属性选择器
选择所有带有v-cloak属性的元素
[v-cloak]{ display:none }
斗篷/幕布
ii. 在要暂时隐藏的元素上,添加v-cloak指令.
3). 原理: 当new Vue()加载完之后,new Vue()会扫描页面中所有v-cloak属性,并自动移除所有v-cloak属性。原来暂时隐藏的元素,就都显示出来了。
b. v-text:
1). 什么是: 专门用来代替{{}}绑定元素内容的特殊指令。
2). 如何: <元素 v-text=“变量”> </元素>
3). 说明: v-text和{{}}原理一样,底层都相当于textContent。
4). 原理: v-text暂时不会把变量值放入元素开始标签到结束标签之间。只有当new Vue()加载完,解析到v-text指令时,才临时读取变量值,放入元素开始标签到结束标签之间。
5). 强调: 如果v-text中的内容是由部分写死的字符串和变量拼接而成,则必须在"“内用``定义模板字符串。”"范围内必须符合js的语法规定。
(3). 示例:分别使用v-cloak和v-text避免用户短暂看到{{}}
9_v-cloak.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="js/vue.js"></script>
<style>
[v-cloak]{ display:none }
</style>
</head>
<body>
<div id="app">
<h3 v-cloak>Welcome {{uname}}</h3>
</div>
<script>
setTimeout(function(){
new Vue({
el:"#app",
data:{
uname:"dingding"
}
})
},2000);
</script>
</body>
</html>
10_v-text.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="js/vue.js"></script>
</head>
<body>
<div id="app">
<h3 v-text="`Welcome ${uname}`"></h3>
</div>
<script>
setTimeout(function(){
new Vue({
el:"#app",
data:{
uname:"dingding"
}
})
},2000)
</script>
</body>
</html>
(8).v-once
(1). 什么是: 专门标记一个元素只在首次加在时更新一次内容。之后,即使变量发生变化,元素内容也不更新.
(2). 如何: <元素 v-once>
(3). 原理: new Vue()扫描到v-once后,只更新一次v-once所在元素的内容。并且不会将v-once加入虚拟DOM树。今后,即使变量值发生变化,也无法通知到这个v-once所在的元素。自然不会更新元素的内容。
(4). 优化: 今后如果发现个别元素只需要在首次页面加载时更新一次,之后不会再更新!都必须用v-once标记。进一步减少虚拟DOM树中元素的个数,进一步提高虚拟DOM树的效率。
(5). 示例: 使用v-once标记一个元素只在首次加载时更新内容
11_v-once.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="js/vue.js"></script>
</head>
<body>
<div id="app">
<h3 v-once>上线时间: {{time}}</h3>
<h3>当前系统时间: {{time}}</h3>
</div>
<script>
var vm=new Vue({
el:"#app",
data:{
time:new Date().toLocaleString()
}
});
setInterval(function(){
vm.time=new Date().toLocaleString();
},1000)
</script>
</body>
</html>
运行结果:
(9).v-pre:
(1). 什么是: 专门保护元素内容中的{{}}不被new Vue()编译的特殊指令
(2). 如何: <元素 v-pre>
(3). 示例: 阻止内容中的{{}}被编译:
12_v-pre.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="js/vue.js"></script>
</head>
<body>
<div id="app">
<h1 v-pre>vue框架采用{{自定义变量名}}方式标记页面中可能发生变化的位置</h1>
</div>
<script>
new Vue({
el:"#app"
})
</script>
</body>
</html>
运行结果:
六.双向绑定:
-
问题: 使用传统的:value绑定表单元素的内容时,虽然界面中更新了表单元素的内容,但是程序中的变量里却无法获得新的表单元素内容。
-
原因: 单向绑定: 只能将程序中的变量值,更新到页面中显示。无法实时将页面中的修改的新值,反向更新回程序中的变量里。(只能从Model->View,不会从View->Model)比如: :value就是一种单向绑定.
-
解决: vue中其实提供了双向绑定的方式:
1). 什么是双向绑定: 既能将程序中的变量值,更新到页面中显示。又能实时将页面中的修改的新值,反向更新回程序中的变量里。(既能从Model->View,又能从View->Model)
2). 何时: 今后,只要想实时获得/收集用户在界面上输入或选择的表单元素的新值时,都要用双向绑定。
3). 如何:<表单元素 v-model=“变量”>
view->model
4). 结果: 只要界面上用户修改了表单元素的值,v-model就会立刻自动将新表单元素值更新回程序中data中的变量里保存。 -
示例: 使用双向绑定实现点按钮搜索
13_v-model.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="js/vue.js"></script>
</head>
<body>
<!--VUE 3步
1. 做界面
1.1 唯一父元素包裹
1.2 找可能发生改变的位置
本例中: 只有文本框的内容可能发生变化,所以用:value来绑定文本框的内容。
1.3 找触发事件的元素
本例中: 点按钮触发事件,执行搜索操作-->
<div id="app">
<input v-model="str">
<button @click="search">百度一下</button>
</div>
<script>
//2. 创建new Vue()对象, 监视id为app的区域
var vm=new Vue({
el:"#app",
//3. 创建模型对象:
//3.1 创建data对象
//本例中: 因为界面中只需要一个变量,所以
data:{
str:"" //保存用户在界面上文本框中输入的内容。开局,内容为""
},
//3.2 创建methods对象
//本例中: 因为界面中只需要一个事件处理函数,所以
methods:{
search(){
if(this.str!==""){
console.log(`搜索${this.str}相关的内容...`);
}
}
}
})
</script>
</body>
</html>
运行结果:
- 原理: 双向绑定的原理: 自动绑定input或change事件+访问器属性+虚拟DOM树
(1). v-model会自动给表单元素绑定input或change事件
(2). 每当界面中表单元素的值发生改变时,就能自动调用事件处理函数。
(3). 在事件处理函数中,可以获得当前表单元素的内容,实时赋值给data中的变量。
(4). 示例: 使用:value和事件模拟实现双向绑定:
13_v-model2.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="js/vue.js"></script>
</head>
<body>
<!--VUE 3步
1. 做界面
1.1 唯一父元素包裹
1.2 找可能发生改变的位置
本例中: 只有文本框的内容可能发生变化,所以用:value来绑定文本框的内容。
1.3 找触发事件的元素
本例中: 点按钮触发事件,执行搜索操作-->
<div id="app">
<!--DOM中每个文本框都有一个input事件
每当在文本框中输入或删除了内容时就会实时触发。-->
<input :value="str" @input="input">
<button @click="search">百度一下</button>
</div>
<script>
//2. 创建new Vue()对象, 监视id为app的区域
var vm=new Vue({
el:"#app",
//3. 创建模型对象:
//3.1 创建data对象
//本例中: 因为界面中只需要一个变量,所以
data:{
str:"" //保存用户在界面上文本框中输入的内容。开局,内容为""
},
//3.2 创建methods对象
//本例中: 因为界面中只需要一个事件处理函数,所以
methods:{
input(e){
//DOM中:
//当前文本框的内容
//赋值给
//data中的str
this.str=e.target .value
},
search(){
if(this.str!==""){
console.log(`搜索${this.str}相关的内容...`);
}
}
}
})
</script>
</body>
</html>
运行结果:
- 按回车执行搜索: vue中的事件修饰符:
(1). 问题: @keyup可以为元素绑定键盘按下后抬起事件。但是,任何按键都可以触发这个事件。不是我们想要的!我们希望只有按回车键时才能触发事件。
(2). 解决: vue中专门定义了事件修饰符用来限制事件的行为:
比如: @keyup.13=“事件处理函数”
只限于按13号键/回车键时才能触发事件。 - watch: (监视)
(1). 什么是: 专门在变量值被修改时自动执行一项任务的特殊监视函数.
(2). 何时: 今后,只要希望一个变量的值一改变,就能立刻执行一个操作时。都用watch
(3). 如何:
new Vue({
el:"#app",
data:{ 变量名: 值, ... },
methods:{},
watch:{
变量名(){
}
}
})
- 示例: 实现按回车搜索和一边输入一边执行搜索
13_v-model3.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="js/vue.js"></script>
</head>
<body>
<!--VUE 3步
1. 做界面
1.1 唯一父元素包裹
1.2 找可能发生改变的位置
本例中: 希望文本框内容改变,程序中变量自动接到新值,就必须用v-model双向绑定.
1.3 找触发事件的元素
本例中: 点按钮触发事件,执行搜索操作-->
<div id="app">
<input v-model="str" @keyup.13="search">
<button @click="search">百度一下</button>
</div>
<script>
//2. 创建new Vue()对象, 监视id为app的区域
var vm=new Vue({
el:"#app",
//3. 创建模型对象:
//3.1 创建data对象
//本例中: 因为界面中只需要一个变量,所以
data:{
str:"" //保存用户在界面上文本框中输入的内容。开局,内容为""
},
//3.2 创建methods对象
//本例中: 因为界面中只需要一个事件处理函数,所以
methods:{
search(){
if(this.str!==""){
console.log(`搜索${this.str}相关的内容...`);
}
}
},
//因为希望str变量值一变,立刻自动执行搜索操作
watch:{
str(){
this.search();
}
}
})
</script>
</body>
</html>
运行结果:
- 不同表单元素上的双向绑定效果:
(1). 文本框/textarea :
v-model会自动获得value属性值,并自动更新回程序中data中的变量里保存。
(2). 单选按钮(radio):
a. 特点: 一组单选按钮有多个radio组成。且每个radio的value值都是固定写死的。
b. 如何: 要在每个备选的radio中都写上v-model=sex
<input type="radio" name="sex" value="1" v-model="sex">男
<input type="radio" name="sex" value="0" v-model="sex">女
c. 原理:
1). html中,一组相同name名的radio,只能选一个
2). v-model每次只会把选中的一个radio的value值自动更新回程序中变量里。
3). 如果程序中的变量值改变,也会影响页面上radio的选中状态。v-model会取回变量值和当前radio的value值做比较。哪个radio的value值与变量值相等,哪个radio就选中。
d. 示例: 选择性别:
16_v-model_radio.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="js/vue.js"></script>
</head>
<body>
<div id="app">
性别: <input type="radio" name="sex" value="1" v-model="sex">男
<input type="radio" name="sex" value="0" v-model="sex">女
<br/>
<h3>您选择的性别是:{{sex}}</h3>
</div>
<script>
new Vue({
el:"#app",
data:{
sex:1
}
})
</script>
</body>
</html>
(3). select:
a. 特点:
1). 一个select元素,包含多个option元素
2). select元素中每个value值也都是在每个option上写死的!
b. 如何: v-model只写在select元素上一份即可!
<select v-model="变量">
<option value="备选值1">xxx</option>
<option value="备选值2">xxx</option>
<option value="备选值3">xxx</option>
</select>
c. 原理:
1). 首次加载页面时: v-model读取出变量的当前值和每个option的value值作比较。哪个option的value值与变量值相等,select就选中哪个option
2). 每当用户切换了select下的选中项时,v-model只会将选中的一个option的value值自动更新回程序中data中变量里保存。
d. 示例:选择城市,显示城市图片
17_v-model_select.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="js/vue.js"></script>
</head>
<body>
<!--VUE 3步
1. 做界面
1.1 唯一父元素
1.2 找可能发生变化的位置:
本例中: 2处:
select的选中项会被用户主动改变,所以,应该用双向绑定
img元素的src属性值会被程序自动改变,所以,应该用单向绑定-->
<div id="app">
请选择城市: <select id="sel" v-model="src">
<option value="img/bj.jpg">北京</option>
<option value="img/sh.jpg">上海</option>
<option value="img/hz.jpg">杭州</option>
</select><br/>
<br/>
<br/>
<img style="width:300px" :src="src">
</div>
<script>
//2. 创建new Vue()对象
new Vue({
el:"#app",
//3. 创建模型对象
//本例中: 界面中虽然有两处变化,但是,两处变化用的是同一个变量。
data:{
src:"img/bj.jpg"
}
})
</script>
</body>
</html>
运行结果:
(4). checkbox单用:
a. 特点: 用户选中与不选中改的是checked属性。
b. 如何:
c. 原理:
1). v-model直接操作和读取checked属性
2). 首次加载页面时,v-model读取变量值。如果变量值为true,则当前checkbox选中。如果变量值为false,则当前checkbox不选中
3). 当用户切换了选中状态,则v-model会直接将checked属性值更新回程序中data中变量里。且值还是bool类型的true或false。
d. 示例: 点同意,启用元素:
18_v-model_checkbox.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="js/vue.js"></script>
</head>
<body>
<!--VUE 3步
1. 做界面
1.1 唯一父元素
1.2 找可能发生变化的位置:
本例中: 2大类情况
checkbox自己的checked状态会被用户主动改变,所以应该用v-model绑定
其它三个表单元素的disabled属性会被程序自动改变,所以应该用:单向绑定。
又因为选中checkbox会影响其它元素的disabled,所以,checkbox和其它元素的disabled属性使用的是同一个变量。
-->
<div id="app">
<br/>
用户名:<input :disabled="!agree"><br/>
<br/>
密码:<input :disabled="!agree" type="password"><br/>
<br/>
<input type="checkbox" v-model="agree">同意<br/>
<br/>
<button :disabled="!agree">注册</button>
</div>
<script>
//2. 创建new Vue()对象
new Vue({
el:"#app",
//3. 创建模型对象
//本例中: 界面上只需要一个变量agree
data:{
agree:false, //表示用户是否同意,开局,默认为不同意
}
})
</script>
</body>
</html>
运行结果:
七. 绑定样式:
- 绑定内联样式(style)的css属性:
(1). 将style属性看成一个普通的字符串属性来绑定:
<元素 :style=“变量”>
data:{
变量:“css属性1:值1; css属性2:值2;…”
}
结果: 变量值会成为style属性的值:
<元素 :style=" css属性1:值1; css属性2:值2;…">
问题: 极其不便于用程序单独修改其中某一个css属性值
(2). 将style属性值看成一个js对象来绑定:
<元素 :style="{ css属性名1:变量1, css属性名2:变量2 }"
data:{
变量1: 值1,
变量2: 值2
}
结果: 2件事:- 用变量值代替{}中css属性值
- 将整个对象语法的{},翻译为字符串格式:
<元素 style=“css属性名1:值1; css属性名2:值2”
问题: 如果多个元素都需要修改同一个css属性,则变量名极容易冲突!
(3). 示例: 控制一架飞机飞行:
19_style.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="js/vue.js"></script>
<style>
img{
position:fixed;
}
</style>
</head>
<body>
<div id="app">
<!-- css属性名 变量名-->
<img :style="{ left:left, top:top }" src="img/p3.png" >
<!--<img :style="{ left, top }" src="img/p3.png" >-->
</div>
<script>
var vm=new Vue({
el:"#app",
data:{
left:"200px", //加单位
top:"100px" //加单位
}
});
//在f12 console控制台:
//vm.left="300px" 让飞机水平向右飞100px
//vm.top="50px" 让飞机水平向上飞50px
//只要在整个窗口中任何位置按下键盘时,都能飞行
window.onkeydown=function(e){
console.log(e.keyCode);
//左37, 上38, 右39, 下40
if(e.keyCode==37){
var left=parseInt(vm.left);
left-=10;//速度
vm.left=left+"px";
}else if(e.keyCode==39){
var left=parseInt(vm.left);
left+=10;//速度
vm.left=left+"px";
}else if(e.keyCode==38){
var top=parseInt(vm.top);
top-=10;
vm.top=top+"px";
}else if(e.keyCode==40){
var top=parseInt(vm.top);
top+=10;
vm.top=top+"px";
}
}
</script>
</body>
</html>
(4). 多个元素都要绑定css属性:
a. 将style对象保存到一个变量中: 2步:
<元素1 :style="变量名1">
<元素2 :style="变量名2">
data:{
变量名1:{
css属性名: 属性值,
left : ...
},
变量名2:{
css属性名: 属性值,
left : ...
},
}
优点: 即使不同的变量内部,包含相同的css属性名,也因为分属于不同的对象范围内,而不会发生冲突
结果: new Vue()会先将变量保存的对象,翻译为style字符串,再用翻译之后的字符串作为style的最终属性值。
(5). 有些css属性是固定的,而有些css属性是变化的:
a. 其实固定不变的内联样式css属性和变化的css属性可以同时并存。
<元素 style=“固定不变的css属性” :style=“变化的css属性”
b. 运行时: new Vue()会先把:style的内容编译为字符串,然后,再将:style字符串与不带:的style的字符串拼接,形成最终一个完整的style字符串.
(6). 示例: 控制两架飞机飞行:
19_style2.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="js/vue.js"></script>
</head>
<body>
<div id="app">
<img style="position:fixed" :style="p3Style" src="img/p3.png" >
<img style="position:fixed" :style="p5Style" src="img/p5.png" >
</div>
<script>
var vm=new Vue({
el:"#app",
data:{
p3Style:{
left:"200px",
bottom:"100px"
},
p5Style:{
left:"500px",
bottom:"150px"
}
}
});
//只要在整个窗口中任何位置按下键盘时,都能飞行
window.onkeydown=function(e){
console.log(e.keyCode);
//左37, 上38, 右39, 下40
if(e.keyCode==37){
var left=parseInt(vm.p3Style.left);
left-=10;//速度
if(left<0){left=0};
vm.p3Style.left=left+"px";
}else if(e.keyCode==39){
var left=parseInt(vm.p3Style.left);
left+=10;//速度
if(left>window.innerWidth-200){
left=window.innerWidth-200;
}
vm.p3Style.left=left+"px";
}else if(e.keyCode==38){
var bottom=parseInt(vm.p3Style.bottom);
bottom+=10;
if(bottom>window.innerHeight-100){
bottom=window.innerHeight-100
}
vm.p3Style.bottom=bottom+"px";
}else if(e.keyCode==40){
var bottom=parseInt(vm.p3Style.bottom);
bottom-=10;
if(bottom<0){bottom=0}
vm.p3Style.bottom=bottom+"px";
}
//左65, 上87, 右68, 下83
if(e.keyCode==65){
var left=parseInt(vm.p5Style.left);
left-=10;//速度
vm.p5Style.left=left+"px";
}else if(e.keyCode==68){
var left=parseInt(vm.p5Style.left);
left+=10;//速度
vm.p5Style.left=left+"px";
}else if(e.keyCode==87){
var bottom=parseInt(vm.p5Style.bottom);
bottom+=10;
vm.p5Style.bottom=bottom+"px";
}else if(e.keyCode==83){
var bottom=parseInt(vm.p5Style.bottom);
bottom-=10;
vm.p5Style.bottom=bottom+"px";
}
}
</script>
</body>
</html>
- 今后只要一个效果需要同时修改多个css属性时,都用class绑定
(1). 不好的方式: 将整个class作为一个字符串变量绑定
不好: 及其不便于只修改其中某一个class
(2). 用对象语法来绑定:
a. 2步:
1). <元素 :class="{ class名1: 变量名1, class名2: 变量名2 }">
2). data:{
变量名1: true或false,
变量名2: true或false,
}
b. 原理: 编译时,先用变量值true或false,代替{}中:后的变量名位置。然后将{}对象编译为最终的class字符串。只有值为true的class,才会进入最终的class字符串中发挥作用。而值为false的class,暂时不会被加入最终的class字符串中,不会起作用。
c. 示例: 带样式的表单验证,验证一个手机号
5_class.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="js/vue.js"></script>
<style>
.success{
background-color:lightGreen;
color:green;
border:green 1px solid ;
}
.fail{
background-color:pink;
color:red;
border:red 1px solid;
}
</style>
</head>
<body>
<!--VUE 3步
1. 做界面
1.1 唯一父元素
1.2 找可能变化的位置:
本例中:
1. input的内容 用户主动输入而改变,所以,用v-model。
span的class,有2种情况:
2. 验证通过用success,不用fail
3. 验证失败用fail, 不用success
4. span的内容
1.3 找可能触发事件的位置
本例中: 一边输入一边触发验证,应该用watch-->
<div id="app">
手机号:<input type="text" v-model="phone">
<span :class="{ success:success , fail:fail }">{{msg}}</span>
</div>
<script>
//2. 创建new Vue()对象
new Vue({
el:"#app",
//3. 创建模型对象:
//3.1 因为界面中共需要4个变量,所以
data:{
phone:"", //保存文本框中用户输入的内容
success:false, //控制是否启用验证通过的样式类
fail:false, //控制是否启用验证失败的样式类
msg:"", //控制错误提示信息
},
//3.2 因为希望手机号一变,立刻执行查询,应该监控phone变量
watch:{
phone(){
//定义正则表达式规定手机号的规则
var reg=/^1[3-9]\d{9}$/;
//用正则表达式验证手机号变量的值
var result=reg.test(this.phone);
//如果验证通过
if(result==true){
//修改span的class,启用success,禁用fail
this.success=true;
this.fail=false;
//修改span的内容为"手机号格式正确"
this.msg="手机号格式正确"
}else{
//修改span的class,禁用success,启用fail
this.success=false;
this.fail=true;
//修改span的内容为"手机号格式不正确"
this.msg="手机号格式不正确"
}
}
}
})
</script>
</body>
</html>
运行结果:
(3). 多个元素都要绑定相同的class
a. 问题: 如果多个元素都要绑定相同的class,则使用相同的变量名一定有冲突
b. 解决: 要将class的绑定对象定义在data中的一个变量里。不要定义在html元素上。
c. 如何: 2步:
1). <元素1 :class=“变量名1”>
<元素2 :class=“变量名2”>
2). data:{
变量名1:{
class名1: true或false,
class名2: true或false
},
变量名2:{
class名1: true或false,
class名2: true或false
},
d. 示例: 验证手机号和密码
5_class2.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="js/vue.js"></script>
<style>
.success{
background-color:lightGreen;
color:green;
border:green 1px solid ;
}
.fail{
background-color:pink;
color:red;
border:red 1px solid;
}
</style>
</head>
<body>
<div id="app">
手机号:<input type="text" v-model="phone">
<span :class="phoneSpan">{{phoneSpan.msg}}</span><br>
密码:<input type="password" v-model="pwd">
<span :class="pwdSpan">{{pwdSpan.msg}}</span>
</div>
<script>
//2. 创建new Vue()对象
new Vue({
el:"#app",
data:{
phone:"",
phoneSpan:{
success:false,
fail:false,
msg:""
},
pwd:"",
pwdSpan:{
success:false,
fail:false,
msg:""
}
},
watch:{
phone(){
var reg=/^1[3-9]\d{9}$/;
var result=reg.test(this.phone);
if(result==true){
this.phoneSpan={
success:true,
fail:false,
msg:"手机号格式正确"
}
}else{
this.phoneSpan={
success:false,
fail:true,
msg:"手机号格式不正确"
}
}
},
pwd(){
var reg=/^\d{6}$/;
var result=reg.test(this.pwd);
if(result==true){
this.pwdSpan={
success:true,
fail:false,
msg:"密码格式正确"
}
}else{
this.pwdSpan={
success:false,
fail:true,
msg:"密码格式不正确"
}
}
}
}
})
</script>
</body>
</html>
运行结果:
(4). 固定不变的class和动态变化的class也可以并存
a. <元素 class=“固定不变的class” :class=“变化的class”>
b. 结果: 两个class中的所有class,会拼接为一个大的class字符串,最终作用域当前元素上
八.自定义指令
- 何时: 如果希望在页面加载时,就对元素执行一些初始化的操作,但是vue中又没有提供对应的指令时,我们就可以添加自定义指令。
- 如何添加自定义指令:
//向Vue类型中
// 添加一种自定义指令
Vue.directive(“自定义指令名”,{
//当元素被加入到页面上之后,自动执行该回调函数
//自动将当前带有这个指令的DOM元素对象
// ↓
inserted(当前DOM元素对象){
对当前DOM元素对象执行原生的DOM操作
}
}) - 如何使用自定义指令:
<元素 v-自定义指令名>
强调: 自定义指令在定义时,指令名一定不要加v-前缀。
但是,在元素开始标签中使用自定义指令时,自定义指令名必须加v-前缀 - 关于命名:
(1). 问题: 自定义指令名不能用驼峰命名!
(2). 原因: html语言不认识大写字母!所有HTML中的大写字母,都会被自动转换为小写字母。可是,一旦浏览器把大写字母转为小写字母,就和原指令名不一致了!
(3). 解决:
a. 只要在html地盘中使用的名称,如果包含多个英文单词,都不能用驼峰命名,必须用-分隔
b. 只要在js地盘中使用的名称,如果包含多个英文单词,都应该用驼峰命名。 - 示例: 定义自定义指令,让元素自动获得焦点
6_directive.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="js/vue.js"></script>
</head>
<body>
<div id="app">
<!--希望在页面加载时,input文本框自动获得焦点-->
<input id="txt" v-my-focus><button>搜索</button>
</div>
<script>
//先定义自定义指令:
Vue.directive("my-focus",{
// 自己改名
inserted(原生DOM元素对象){
/******原生DOM的地盘**********/
//想让当前DOM元素自动获得焦点
//原生DOM中,已经提供了一个方法
原生DOM元素对象.focus();
/******原生DOM的地盘**********/
}
})
new Vue({
el:"#app"
})
</script>
</body>
</html>
运行结果:
九. 计算属性:
- 问题:
(1). 如果界面中要使用的值不是现成的,需要经过复杂的计算(用分支或循环)才能获得
(2). 但是,界面中{{}}里不支持分支,循环等复杂程序逻辑。 - 解决: 今后,如果界面上需要一个值不是现成的,必须经过其它变量值复杂计算才能获得时,都用计算属性
- 如何: 2步:
(1). 定义计算属性:
new Vue({
el:"#app",
data:{},
methods:{},
watch:{},
computed:{ //专门定义计算属性的区域
//计算属性虽然称为"属性",但是,因为要包含复杂的计算过程,所以,本质上却是一个函数。
属性名(){
//复杂的结算过程
return 结算结果
}
}
})
(2). 在界面中使用计算属性: 计算属性和用法和data中普通变量的用法完全一样!
<元素>{{计算属性名}}</元素>
强调: 不要加()
4. 原理:
(1). new Vue()扫描到一个不认识的变量时,都会先去data中查找。如果data中没有,再自动去computed中查找是否有同名的计算属性。
(2). 自动执行计算属性的函数,并将返回值替换界面中计算属性名所在的位置。
5. 计算属性 vs methods中方法 区别
(1). methods中的方法: 反复调用几次,就重复执行几次,每次执行结果,不缓存执行结果,不能为下一次服务。
(2). 计算属性:
a. 只在第一次使用时,计算一次。然后, new Vue()就会立刻将计算结果缓存起来。
b. 之后如果使用相同的计算属性时,则不用重新计算,直接取出缓存中的值,反复使用。——无需重复计算,效率高!
c. 除非当计算属性内部依赖的其它普通变量值发生了改变时,才重新计算计算属性的新值,并立刻将新值重新缓存起来。
6. 总结:
(1). 今后,如果更侧重于执行一项操作,而不是侧重返回一个值时,就首选methods。比如: 登录、格式验证等
(2). 今后,如果更侧重计算一个结果值时,就首选computed。比如:计算总价。
7. 示例: 定义计算属性,计算购物车总价:
7_computed.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="js/vue.js"></script>
</head>
<body>
<div id="app">
<h3>总价: ¥{{total.toFixed(2)}}</h3>
<ul>
<li v-for="(p,i) of cart" :key="i">
{{p.pid}} | {{p.pname}} | ¥{{p.price.toFixed(2)}} | {{p.count}} | 小计: ¥{{(p.price*p.count).toFixed(2)}}
</li>
</ul>
<h3>总价: ¥{{total.toFixed(2)}}</h3>
</div>
<script>
new Vue({
el:"#app",
data:{
//购物车数组
cart:[
//商品对象
{pid:1, pname:"华为", price:5588, count:2},
//商品对象
{pid:2, pname:"苹果", price:9588, count:1},
//商品对象
{pid:3, pname:"小米", price:3588, count:3}
]
},
methods:{
},
computed:{
//专门计算购物车总价的计算属性
total(){
console.log(`自动调用了一次计算属性total()`);
var result=0;
for(var p of this.cart){
result+=p.price*p.count;
}
return result;
}
}
})
</script>
</body>
</html>
运行结果:
十. 过滤器:
- 问题: 程序中,有些值不能直接给人看!
- 比如: 性别, 时间, … …
- 解决: 今后,只要一个值,需要加工后,才能给人看时,都应该用过滤器。
- 如何: 2步:
(1). 定义过滤器:
//在Vue类型中
//添加一个过滤器 ↓
Vue.filter("过滤器名", function(原值){
对变量的原值进行加工
← return 加工后的结果
})
(2). 使用过滤器: 在绑定语法中:
<元素>{{变量 | 过滤器名}}</元素>
连接
(3). 因为过滤器名称将来会用在{{}}绑定语法内。又因为{{}}内是js的地盘,所以过滤器名如果遇到多个单词必须用驼峰。
- 原理: new Vue()扫描到|时,就会自动调用|后的过滤器函数,并自动将|左边变量的原始值自动传给过滤器函数的第一个形参。过滤器函数内部,根据传入的变量旧值加工出新值,替换{{}}位置。
- 示例: 过滤性别:
8_filter.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="js/vue.js"></script>
</head>
<body>
<div id="app">
<!--不想显示1和0,想显示男和女-->
<!--html地盘-->|<!-- js地盘 -->|
<h3>性别: {{sex | sexFilter}}</h3>
</div>
<script>
//先定义过滤器: 1 或 0
Vue.filter("sexFilter",function(变量的原值){
if(变量的原值==1){
return "男"
}else{
return "女"
}
})
new Vue({
el:"#app",
data:{
sex:1
}
})
</script>
</body>
</html>
运行结果:
性别: 男
- 其实,过滤器也可以传参:
(1). 2步:
a. 定义过滤器时:
Vue.filter(“过滤器名”,function(原值, 自定义形参, …){
return xxx
})
b. 使用过滤器时:
<元素>{{变量 | 过滤器名(实参值, …) }}
(2). 示例: 过滤不同语言的性别
8_filter2.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="js/vue.js"></script>
</head>
<body>
<div id="app">
<!--默认显示中文的男或女-->
<h3>性别: {{sex | sexFilter}}</h3>
<!--通过参数改为显示英文的男或女-->
<h3>性别: {{sex | sexFilter("en")}}</h3>
<!--通过参数改为显示中文的男或女-->
<h3>性别: {{sex | sexFilter("cn")}}</h3>
</div>
<script>
Vue.filter("sexFilter",function(原值, 语言){
if(语言=="en"){
return 原值==1?"Male":"Female"
}else{
return 原值==1?"男":"女"
}
})
new Vue({
el:"#app",
data:{
sex:1
}
})
</script>
</body>
</html>
运行结果:
性别: 男
性别: Male
性别: 男
- 多个过滤器连用
(1). 如何: 使用过滤器时:
<元素>{{变量 | 过滤器1 | 过滤器2 | … }}</元素>
(2). 强调: 除第一个过滤器之外,之后其余过滤器接到的都不是变量的原始值了,而是前一个相邻的过滤器返回的中间产物。
(3). 示例: 给性别之后拼接图标(♂,♀)
9_filter3.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="js/vue.js"></script>
</head>
<body>
<div id="app">
<!--只显示图标-->
<h3>性别: {{sex | sexIcon}}</h3>
<!--默认显示中文+图标-->
<h3>性别: {{sex | sexFilter | sexIcon}}</h3>
<!--通过参数改为英文+图标-->
<h3>性别: {{sex | sexFilter("en") | sexIcon}}</h3>
<!--通过参数改为中文+图标-->
<h3>性别: {{sex | sexFilter("cn") | sexIcon}}</h3>
</div>
<script>
Vue.filter("sexFilter",function(原值, 语言){
if(语言=="en"){
return 原值==1?"Male":"Female"
}else{
return 原值==1?"男":"女"
}
})
// 1,0,男,女,Male,Female
Vue.filter("sexIcon",function(原值){
if(原值==1){
return "♂"
}else if(原值==0){
return "♀"
}else if(原值=="男"||原值=="Male"){
return 原值+"♂"
}else if(原值=="女"||原值=="Female"){
return 原值+"♀"
}
})
new Vue({
el:"#app",
data:{
sex:0
}
})
</script>
</body>
</html>
运行结果:
性别: ♀
性别: 女♀
性别: Female♀
性别: 女♀
十一. axios:
- 什么是: 专门在vue中发送ajax请求的基于Promise的第三方函数库。
- 如何:
(0). 先配置服务器端接口地址的基础url部分:
axios.defaults.baseURL="http://xzserver.applinzi.com"
执行时,axios会将baseURL和相对url地址拼接在一起形成完整的服务器端接口地址。
(1). 发送get请求:
axios.get("服务器端接口的相对url地址",{
params:{
变量名: 变量值, 变量名: 变量值
}
}).then((result)=>{ //回调函数,请求响应成功后自动执行
//result里放的不直接是返回结果
//result.data里放的才是服务器端真实的返回结果
}).catch((err)=>{ //回调函数,请求响应失败后自动执行
//err错误提示信息
})
axios自动调用JSON.parse,将返回的json字符串转为js内存中的数组或对象。不用我们写任何东西。
(2). 发送post请求:
axios.post(
"服务器端接口的相对url地址",
"变量名=变量值&变量名=变量值&..."
).then((result)=>{
result.data是服务器端返回的结果
})
- 示例: 使用axios发送get和post请求:
10_axios.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="js/axios.min.js"></script>
</head>
<body>
<script>
axios.defaults.baseURL="http://xzserver.applinzi.com";
//向东哥新浪云服务器发送请求
//1. 请求首页6个商品对象
axios.get(
"index")
.then((result)=>{
console.log(result.data);
})
//2. 查询5号商品的详细信息
axios.get(
"details",
{
params:{ lid:5 }
}
).then((result)=>{
console.log(result.data);
})
//3. 登录验证
//(正确的用户名:dingding, 正确的密码:123456)
//乱写验证不通过
axios.post(
"users/signin",
"uname=dingding&upwd=123456"
).then((result)=>{
console.log(result.data);
})
</script>
</body>
</html>
运行结果:
十二. vue生命周期:
- 什么是: 一个new Vue()对象,创建,更新,销毁的全过程。
- 包括: 4个
(1). 创建(create):
a. 创建new Vue()对象及其内部的所有子内容,并为data中每个变量,自动请保镖。
b. 但是,暂时没有虚拟DOM树,也没有加载页面中的元素内容。
(2). 挂载(mount):
a. 扫描真实DOM树,生成虚拟DOM树,并首次将数据加载到页面元素中显示。
b. 既有访问器属性又有虚拟DOM树——vue对象完成。
********必经阶段**********
(3). 更新(update): 只有修改了data中的变量,还影响了页面中的显示时,才会触发更新阶段,重新挂载DOM树内容。
(4). 销毁(destroy): 只有主动调用$destroy()函数销毁当前new Vue()对象时,才触发。 - 如何在对应的生命周期阶段中自动执行想要的操作
(1). 普通网页: 想在页面加载后自动执行: 2个事件
a. DOMContentLoaded
b. window.οnlοad=funtion(){ … }
(2). vue中: 8个生命周期钩子函数
beforeCreate(){ … }
a. 创建create
created(){ … }
beforeMount(){ … }
b. 挂载mount
mounted(){ … }
beforeUpdate(){ … }
c. 更新update
updated(){ … }
beforeDestroy(){ … }
d. 销毁destroy
destroyed(){ … } - 在哪里发送axios请求:
(1). axios请求->数据显示到页面的全过程:
a. axios发送请求
b. 接收响应并赋值给data中一个变量中保存
c. 在页面中用v-for绑定响应结果中的每条数据
(2). 要求在发送axios请求时,应该已经具备data对象和虚拟DOM树。
(3). 在哪里发送axios请求:
a. beforeCreate(),连data都没有,不可能保存响应结果
b. created()和beforeMount(),暂时没有虚拟DOM树,所以,即使axios响应回来,也无法渲染DOM树内容
c. 最合适的位置: mounted(),此时既有data对象可以保存响应结果,又有虚拟DOM树,可以挂在页面内容。
十三.组件:
-
什么是: 可反复使用的页面中独立的功能区域。
-
为什么: 重用
-
何时: 今后,只要项目中,一块独立的功能区域,可能被反复使用,都要定义成组件
-
如何: 2步
(1). 定义组件,包含HTML+JS内容:
//在Vue类型中
// 添加一个新组件
Vue.component(“组件名”,{
//每个组件其实都是缩微的new Vue()对象
//功能和new Vue()完全一样
//只不过监控的区域小点儿
//第一步,还是做界面,只不过界面要在Vue.component()内部来做,必须写在template:``属性中,代替原来的el:"#app"
el:"#app", //去查找id为app的div所在的区域
//模板
template:<唯一父元素>
组件的HTML片段内容——和以前界面的定义完全一样
</唯一父元素>`
data:{ … },
data(){ //从今往后data一律是函数!
return { //new Object()
界面所需的所有变量
}
}methods:{ … },
watch:{ … },
computed:{ … },
mounted(){ … },
… …
})
(2). 在页面中使用组件: vue中的组件在使用上,其实就是一个可重用的自定义HTML标签而已!组件名叫什么,标签名就叫什么.强调: 因为组件名就是将来的标签名,又因为HTML语言不区分大小写!所以如果组件名使用驼峰命名,到了HTML中是区分不出来的!所以,组件名中如果包含多个英文单词,应该使用-分隔,而不应该用驼峰命名!比如: 在HTML中<myCounter>和<MYCOUNTER>和<mycounter>其实是同一个标签! -
原理: 每当new Vue()扫描页面时发现不认识的自定义HTML标签时,就会去Vue类型中查找是否有同名的自定义组件。只要找到同名的自定义组件,就自动做3件事:
(1). 用组件的template中代码片段代替页面上<自定义组件>标签的位置
(2). 自动调用data(),为当前组件副本创建一个专属的新的data对象,保存变量。
(3). 为当前组件副本区域创建一个缩微版的new Vue()对象,监控组件小范围内的所有数据变化和功能
-
为什么data要变成一个函数:
便于反复调用,创建新对象,避免组件间数据冲突. -
示例: 自定义计数器组件
12_component.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="js/vue.js"></script>
</head>
<body>
<div id="app">
<ul>
<li><my-counter></my-counter></li>
<li><my-counter></my-counter></li>
<li><my-counter></my-counter></li>
</ul>
</div>
<script>
//做一个组件和做new Vue()完全一样:3步
//和前三天所讲的内容完全一致
Vue.component("my-counter",{
//1. 做界面
//1.1 唯一父元素
//1.2 查找可能发生变化的位置
//1.3 触发事件的元素
template:`<div>
<button @click="minus">-</button>
<span>{{n}}</span>
<button @click="add">+</button>
</div>`,
//2. 创建模型对象
//2.1 创建data
data(){//可反复调用的函数
return { //相当于以前的data对象
n:0
}
},
/*****以下和new Vue()完全一样*********/
//2.2 创建methods
methods:{
add(){
this.n++;
},
minus(){
if(this.n>0){
this.n--
}
}
}
})
new Vue({
el:"#app"
})
</script>
</body>
</html>
运行结果:
十四.组件化开发:
- 问题: 现代前端页面功能又多又复杂,如果再靠一个人写一个页面的方式,工期会很长。但是,操作系统规定,一个文件,同一时刻是能被一个人修改!所以,无法多人协作开发!
- 解决: 组件化开发。
(1). 什么是: 拿到一个页面,先划分组件,然后将每个组件的内容,分别保存在多个组件文件中。由多人协作开发一个大的页面。最后运行时,还要将所有组件重新拼接回一个大的页面一起运行!
(2). 优点: 2个:
a. 快
b. 松耦合: 一个组件出错,不影响其它组件先上线使用。
(3). 何时: 今后所有框架都是以组件化开发出来的! - 如何组件化开发:
(1). 拿到页面后,先划分组件区域: 3个原则:
a. 位置: 从上到下,从左到右,由外向内
b. 是否重用:
(2). 为每个组件创建独立的js文件保存组件内容。并把所有组件引入到HTML页面中重新拼接起来,整体展示 - 问题: 用Vue.component()创建的组件,可以放在任何位置,而没有限制。
- 解决: 其实,vue中一切都是组件,但是组件分为3大类:
(1). 根组件(root): new Vue()
a. 监控整个页面
b. 今后,一个项目中几乎都只有一个new Vue()。其余组件,都是new Vue()内部的子组件而已。
(2). 全局组件: Vue.component()
a. 可在任意位置摆放的组件,没有限制!
(3). 子组件:
a. 什么是: 只能在规定的父组件范围内使用的组件
b. 何时: 今后,只要想规定一个子组件,只能在父组件内使用,超出父组件范围不能用时,都要创建子组件
c. 如何: 3步:
1). 只定义一个普通的js对象来保存组件的内容。但是,对象的内容必须符合vue组件的规定!
var 子组件对象名(驼峰命名)={
template:html片段
,
data(){ return { … } },
methods:{ … },
…
}
比如: var todoAdd={ … }
2). 在父组件对象中添加components,来规定哪些组件只能在当前父组件范围内使用.
父组件对象:{
template:xxx
,
data(){ return { … } },
methods:{ … },
…,
components:{ 子组件对象名, … }
}
比如:
Vue.component(“todo”,{
… ,
components:{ todoAdd, }
特异功能 自动↓转换
todo-add
})
3). 在父组件template中使用子组件
<子组件标签名>
比如:
<todo-add>
d. 结果: 子组件只能在父组件范围内使用! - 父子组件的生命周期执行顺序:
父组件beforeCreate
父组件created
父组件beforeMount
子组件beforeCreate
子组件created
子组件beforeMount
子组件mounted
父组件mounted - 示例: 为待办事项功能拆分组件再拼接回页面
1_todo/todo.js
Vue.component("todo",{
template:`<div>
<h3>待办事项列表</h3>
<todo-add></todo-add>
<todo-list></todo-list>
</div>`,
components:{ todoAdd, todoList },
beforeCreate(){
console.log(`父组件beforeCreate`);
},
created(){
console.log(`父组件created`);
},
beforeMount(){
console.log(`父组件beforeMount`);
},
mounted(){
console.log(`父组件mounted`);
},
})
1_todo/todoList.js
var todoList={
template:`<ul>
<li><todo-item></todo-item></li>
<li><todo-item></todo-item></li>
<li><todo-item></todo-item></li>
</ul>`,
components:{ todoItem }
}
1_todo/todoItem.js
var todoItem={
template:`<span>
1 - 吃饭 <a href="javascript:;">×</a>
</span>`
}
1_todo/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="js/vue.js"></script>
<!--因为加载父组件时,就要用到子组件的内容了,所以先引入子组件,然后再引入父组件-->
<script src="todoAdd.js"></script>
<script src="todoItem.js"></script>
<script src="todoList.js"></script>
<script src="todo.js"></script>
</head>
<body>
<div id="app">
<todo></todo>
<!-- <todo-item></todo-item> -->
</div>
<script>
new Vue({
el: "#app"
})
</script>
</body>
</html>
运行结果:
- 组件间传值: 5种:
(1). 父给子:
a. 问题: 父组件中的成员,子组件竟然无法直接使用
b. 原因: Vue中组件的HTML+CSS+JS+数据都是专属的。出了组件都无法使用
c. 解决: 其实,父组件可通过专门的手段,给子组件传递数据。
d. 如何: 3步:
1). 父组件template中:
//父组件将自己的变量值装进子组件身上的某个自定义属性中
<子组件 :自定义属性名=“父组件变量名”>
2). 子组件对象中:
//从自己身上的指定属性中取出父组件存入的值
props:[“自定义属性”]
3). 在子组件中,props中的属性用法和data中的自有变量用法完全一样:
i. 都可用于绑定语法中: {{自定义属性}}
ii. 都可在js程序中用this.自定义属性
(2). 子给父(扩展)
(3). 兄弟间(扩展)
(4). $refs(扩展)
(5). vuex(倒数第二天讲) - 示例: 实现待办事项列表功能
2_todo/todo.js
Vue.component("todo",{
template:`<div>
<h3>待办事项列表</h3>
<todo-add :tasks="tasks"></todo-add>
<todo-list :tasks="tasks"></todo-list>
</div>`,
data(){
return {
tasks:[ "吃饭","睡觉","打亮亮" ]
}
},
components:{ todoAdd, todoList },
beforeCreate(){
console.log(`父组件beforeCreate`);
},
created(){
console.log(`父组件created`);
},
beforeMount(){
console.log(`父组件beforeMount`);
},
mounted(){
console.log(`父组件mounted`);
},
})
2_todo/todoAdd.js
var todoAdd={
props:["tasks"],
template:`<div>
<input v-model="t"><button @click="add">+</button>
</div>`,
data(){
return { t:"" }
},
methods:{
add(){
if(this.t!==""){
this.tasks.push(this.t);
//添加成功,清除t的内容
//清除t,等效于清除文本框内容
this.t="";
}
}
},
beforeCreate(){
console.log(`子组件beforeCreate`);
},
created(){
console.log(`子组件created`);
},
beforeMount(){
console.log(`子组件beforeMount`);
},
mounted(){
console.log(`子组件mounted`);
},
}
2_todo/todoList.js
var todoList={
//从自己身上的属性中获取指定属性名的属性值
props:["tasks"],
template:`<ul>
<li v-for="(t,i) of tasks">
<todo-item :t="t" :i="i" :tasks="tasks"></todo-item>
</li>
</ul>`,
components:{ todoItem }
}
2_todo/todoItem.js
var todoItem={
//从自己身上的t属性和i属性中取出属性值
props:["t", "i", "tasks"],
template:`<span>
{{i+1}} - {{t}} <a href="javascript:;" @click="del">×</a>
</span>`,
methods:{
del(){
this.tasks.splice(this.i,1);
}
}
}
2_todo/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="js/vue.js"></script>
<!--因为加载父组件时,就要用到子组件的内容了,所以先引入子组件,然后再引入父组件-->
<script src="todoAdd.js"></script>
<script src="todoItem.js"></script>
<script src="todoList.js"></script>
<script src="todo.js"></script>
</head>
<body>
<div id="app">
<todo></todo>
<!-- <todo-item></todo-item> -->
</div>
<script>
new Vue({
el: "#app"
})
</script>
</body>
</html>
运行结果:
十五 SPA(Single Page Application):
单 页面 应用
- 什么是: 整个应用程序中,只有一个唯一完整的HTML页面。其余所谓的"页面",其实都是组件而已。切换"页面",其实只是在一个页面中切换不同的组件显示而已。
- 为什么: 单页面 vs 多页面
多页面 | 单页面 | |
---|---|---|
请求次数 | 每访问一个页面,哪怕是曾经访问过的页面,都需要重新向服务器发送请求。——请求次数多 | 只在首次加载时,将唯一完整的HTML页面和所有页面组件都下载到客户端本地。今后,每次切换"页面",只不过是在客户端本地一个html文件中切换不同的组件对象显示而已。无需重复向服务器发送请求。——请求次数少 |
多个页面共用的公共资源 | 每请求一个页面,都要重新下载页面头部引入的css和js文件——请求次数多 | 每次切换页面时,唯一完整的HTML文件是不动的。只更新文件中局部的内容。所以,不会重新加载/请求HTML文件头部引入的css和js。所有css和js也只是在首次加载时下载一次即可。——请求次数少 |
页面加载效率 | 每加载一个新页面,都要删除原来的整棵DOM树,重建整棵DOM树。——效率低 | 每次切换页面时,只是局部更新页面中组件片段的内容,所以不会删除整棵DOM树,只需要修改原DOM中个别元素对象节点即可!——效率高! |
切面切换动画 | 几乎不可能实现页面切换动画。因为每次切换页面总是先彻底删除原页面内容,再重新下载新页面内容。新旧两个页面不可能并存。 | 有可能实现页面过去动画。因为所有组件都已经在客户端了。完全有可能让前后两个页面组件同时显示,来实现过渡动画效果。 |
-
单页面应用的缺点: 首屏加载慢——因为不但下载首页,还要下载其他页面的组件备用。
早就被解决了: 懒加载 -
何时: 今后几乎所以移动端,以及大部分PC端项目都使用单页面应用开发。
-
如何: 3步
(1). 先创建一个唯一完整的HTML页面
a. 拥有基本的vue页面结构的页面
b. 引入vue-router.js路由器组件文件
c. 在唯一完整的页面中使用标签为将来的页面组件设置预留区域
(2). 为每个"页面"创建对应的页面组件(就是子组件,只不过当做页面来用)
将所有页面组件的文件,引入唯一完整的HTML页面中
(3). 创建路由器对象和路由字典
a. 什么是路由器对象: 可自动侦测浏览器地址栏的url变化,根据用户输入的不同地址,自动找到对应的组件对象,帮我们替换到唯一完整的HTML页面中区域
b. 什么是路由字典: 集中保存不同地址与组件对象之间对应关系的数组
c. 为什么要使用路由字典: 路由器对象侦测到地址栏变化时,必须去路由字典中找到对应的路径,才能进一步找到对应的组件对象。
d. 如何创建路由器对象: 3步: 创建独立的router.js文件中
1). 先创建路由字典数组:
var routes=[
{ path:"/", component: 首页组件对象 },
{ path:"/其它路径", component: 其它页面组件对象 },
… …
]
2). 创建路由器对象,并将路由字典装入路由器对象中
var router=new VueRouter({ routes })
3). 将路由器对象引入new Vue()中: 回到唯一完整的HTML页面中:
先将router.js文件引入唯一完整的HTML页面中,
再new Vue({ el:"#app", router }) -
公共的页头组件:
(1). 创建独立的页头组件文件,将页头组件创建为全局组件,因为页头不属于任何一个页面,可用在任意页面中
Vue.component(“my-header”, { … })
(2). 在唯一完整的HTML页面中引入页头组件文件
(3). 问题: 标签放在那儿?2种:
a. 如果所有页面都有统一的页头,则可以将放在唯一完整的HTML页面中外的上方!
b. 如果个别页面不希望有页头!则也可以将标签只放在需要页头的页面组件中! -
如果用户路径错误: 自制一个友好的404页面
(1). 创建一个子组件保存404页面的内容
(2). 去路由字典中加入404页面组件对应的路径
{ path:"", component: 404页面组件对象 }
强调: path:""必须写在其它路由最后!*代表一切路径!
(3). 将404页面组件引入到唯一完整的HTML页面中 -
原理:
(1).路由器router对象被加入到唯一html页面内的new Vue()中,它就会监视当前html页面所在路径。
(2).一旦浏览器地址栏中路径发送变化,router就会立刻获得当前新路径,自动去路由字典routers中查找是否有匹配的路径。
(3).只要找到路由字典中和当前地址栏中匹配的路径,就自动去内存中找对应的组件对象。
(4).只要找到组件对象,就会用组件对象的内容,替换html页中占位符的位置。
(5).最后用户就看到了对应组件的内容。 -
为什么使用#/作为客户端导航的标志
(1).如果在浏览器地址栏输入新地址,默认浏览器都会向服务器发送请求。
(2).但是,vue spa应用中,因为早就把所有的页面的组件都下载到了客户端本地。所以,当修改地址栏路径时,不应该再想服务器
发送请求。而应该交给网页本地的路由器对象,解析新地址,加载对应的组件。
(3).url标准中只有一种地址是客户端导航–锚点地址。当在浏览器内切换锚点地址时,浏览器是不会向服务器发送请求的,而是在
页面内跳转。
(4)所以,Vue单页面应用为了避免浏览器向服务器发送请求,才用的"#/相对路径"锚点地址方式作为客户端导航。 -
示例: 使用单页面应用程序实现一个网站,包含三个页面一个页头
spa/js/index.js
var Index={
template:`<div>
<my-header></my-header>
<h1 style="color:green">这里是首页</h1>
</div>`
}
spa/js/details.js
var Details={
template:`<div>
<my-header></my-header>
<h1 style="color:orange">这里是详情页</h1>
</div>`
}
spa/js/not-found.js
var NotFound={
template:`<div>
<h1 style="color:red">404: 你迷路了!</h1>
</div>`
}
spa/js/my-header.js
Vue.component("my-header",{
template:`<div>
<h1 style="color:blue">这里是页头</h1>
<hr>
</div>`
})
spa/js/router.js
var routes=[
{path:"/", component: Index},
{path:"/details", component: Details},
{path:"*", component: NotFound}
];
var router=new VueRouter({ routes })
spa/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!--1. 引入vue和vue-router基础文件-->
<script src="js/vue.js"></script>
<script src="js/vue-router.js"></script>
<!--2. 引入全局组件和页面组件-->
<script src="js/my-header.js">
//Vue.component("my-header")
</script>
<script src="js/index.js">
//var Index={}
</script>
<script src="js/details.js">
//var Details={}
</script>
<script src="js/not-found.js">
//var NotFound={}
</script>
<!--3. 最后引入路由器组件-->
<script src="js/router.js">
//var routes=[
//用到了Index和Details两个对象名
//]
//var router=new VueRouter({ routes })
</script>
</head>
<body>
<div id="app">
<!-- <my-header></my-header> -->
<router-view>
<!--为将来的页面组件预留区域-->
</router-view>
</div>
<script>
new Vue({
el:"#app",
router
})
</script>
</body>
</html>
-
页面跳转: 2种 固定套路!记住!
(1). 在页面上写死一个标签:
a. 不要用<a>
b. 改为用<router-link to="/相对路径">文本</router-link>
c. 原理:<router-link to>在编译后,也会自动翻译为<a href>
(2). 在js程序中跳转:
this.$router.push("/相对路径")
路由器对象 -
路由传参:
(1). 需求: 实际项目中,前一个页面,跳转到后一个页面时,前一个页面有时会传递一个数据到后一个页面中继续使用!
(2). 多页面应用: 使用url中search查询字符串方式传递:
a. 前一个页面:<a href=“下一个页面.html?变量名=变量值”>
b. 下一个页面:
1). location.search 可获得 “?变量名=变量值”
2). location.search.split("=")[1] 可获得传过来的变量值(3). vue单页面应用程序中:
a. 修改路由字典中的项目,允许携带参数:
{ path:"/相对路径/:自定义变量名", component: 页面组件, props:true }
允许携带一个参数变量
props:true, 让参数自定义变量自动成为下一个页面的props中的属性。在下个页面中就可直接使用自定义变量名获取传过来的参数值
b. 跳转时,路径携带参数值:
<router-link to=/相对路径/参数值
或
this.$router.push("/相对路径/参数值")
c. 在下一个组件中如何接:
1). 先props:[ “自定义变量” ]
2). 在下一个组件中就可以像使用自己的data中的变量一样,使用页面跳转时出来的参数值
(4). vue中规定: 如果一个路径要求携带参数,每次跳转时都必须携带参数值!
否则如果将来跳转时不携带参数,就不允许跳转!而是转向404页面! -
示例: 为上一个单页面应用示例加上跳转页面功能,以及在跳转时传参
spa/js/index.js
var Index={
template:`<div>
<my-header></my-header>
<h1 style="color:green">这里是首页</h1>
<router-link to="/details/5">查看5号商品的详情</router-link><br/>
<button @click="goto(10)">查看10号商品的详情</button>
</div>`,
methods:{
goto(lid){
this.$router.push(`/details/${lid}`);
}
}
}
spa/js/details.js
var Details={
//因为路由字典中写了props:true
//所以lid参数变量会自动成为props中的属性
props:["lid"], //和data中的变量用法完全一样,只不过来源不同而已。props来自于外部,data是自己创建的。
template:`<div>
<my-header></my-header>
<h1 style="color:orange">这里是详情页</h1>
<h2>这里是{{lid}}号商品的详细信息...</h2>
<button @click="back">返回首页</button>
</div>`,
methods:{
back(){
this.$router.push("/");
}
}
}
spa/js/not-found.js
var NotFound={
template:`<div>
<h1 style="color:red">404: 你迷路了!</h1>
</div>`
}
spa/js/my-header.js
Vue.component("my-header",{
template:`<div>
<h1 style="color:blue">这里是页头</h1>
<ul>
<li><router-link to="/">首页</router-link></li>
<li><router-link to="/details">详情页</router-link></li>
</ul>
<hr>
</div>`
})
spa/js/router.js
var routes=[
{path:"/", component: Index},
//允许/details路径携带一个变量名为lid
//props:true意为,让lid变量自动成为details组件中的props属性
{path:"/details/:lid", component: Details, props:true},
{path:"*", component: NotFound}
];
var router=new VueRouter({ routes })
spa/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!--1. 引入vue和vue-router基础文件-->
<script src="js/vue.js"></script>
<script src="js/vue-router.js"></script>
<!--2. 引入全局组件和页面组件-->
<script src="js/my-header.js">
//Vue.component("my-header")
</script>
<script src="js/index.js">
//var Index={}
</script>
<script src="js/details.js">
//var Details={}
</script>
<script src="js/not-found.js">
//var NotFound={}
</script>
<!--3. 最后引入路由器组件-->
<script src="js/router.js">
//var routes=[
//用到了Index和Details两个对象名
//]
//var router=new VueRouter({ routes })
</script>
</head>
<body>
<div id="app">
<!-- <my-header></my-header> -->
<router-view>
<!--为将来的页面组件预留区域-->
</router-view>
</div>
<script>
new Vue({
el:"#app",
router
})
</script>
</body>
</html>
运行结果:
十六. 脚手架:
- 问题: 不同公司,不同团队,不同的人搭建的项目结构、文件名千差万别!没有标准。
- 解决: 今后,任何公司,任何团队,任何人只要搭建框架项目都要用脚手架。
- 什么是脚手架: 对一个项目标准结构的统一规定!
- 为什么: 标准化
- 如何: 2步:
(1). 先安装能够反复生成脚手架的命令行工具: (老母鸡)
npm config set registry https://registry.npm.taobao.org
npm i -g @vue/cli
结果: + @vue/cli@4.5.13 说明装完。
(2). 用工具为不同项目反复生成多套脚手架: (老母鸡下的蛋)
今后,每开发一个新项目,都要用脚手架命令行工具,创建一套新的标准化的脚手架项目结构:
在想创建项目文件夹的路径中,地址栏输入cmd,回车
vue create 自定义项目名
a. ? Please pick a preset: (Use arrow keys)
Default ([Vue 2] babel, eslint)
Default (Vue 3) ([Vue 3] babel, eslint)
> Manually select features
b. ? Check the features needed for your project: (Press <space> to select, <a> to toggle all, <i> to invert selection)
( ) Choose Vue version
(*) Babel //可将时髦的ES6/7等语法翻译为大多数浏览器都支持的ES5代码。
( ) TypeScript
( ) Progressive Web App (PWA) Support
(*) Router //SPA应用的核心
( ) Vuex
( ) CSS Pre-processors //sass/scss
( ) Linter / Formatter
( ) Unit Testing
( ) E2E Testing
c. ? Use history mode for router? (Requires proper server setup for index fallback in production) (Y/n) N
1). Vue的路由导航方式有2种:
a. hash方式: #/相对路径
问题: 万一,页面中也用到#锚点地址在页面内跳转位置。
b. history方式: /相对路径
问题: 浏览器不知道该把请求发给服务器端接口,还是发给客户端的VueRouter()
解决: 后端工程师采用首页重定向技术解决。
d. ? Where do you prefer placing config for Babel, ESLint, etc.? (Use arrowkeys)
In dedicated config files
> In package.json
e. Save this as a preset for future projects? (y/N) N
f. 结果: 看到: Successfully created project xzvue. 说明安装成功
g. 在我们希望创建脚手架项目的位置,多出一个xzvue文件夹。
h. 删除新生成的xzvue文件夹内的.git文件夹
-
在vs code中运行脚手架项目:
(1). 用vs code打开xzvue文件夹。(不要打开xzvue的上级文件夹)
(2). 右键单击package.json文件,选择"在集成终端中打开"
(3). 在终端中启动脚手架项目: 输入npm run serve
!!!结尾没有r
看到:App running at:
- Local: http://localhost:8080/
启动成功
(4). (如果上一步powershell不能用, 才需要)在终点上方的+图标右侧有一个下拉箭头,打开,选择Command Prompt。
(5). 打开浏览器,地址栏中输入http://localhost:8080
看到: 脚手架项目的示例页面 -
脚手架的文件夹结构: SPA 4部分
(1). 唯一完整的HTML页面: 被一分为三:
a. HTML的基础结构保存在了public/index.html
b. <div id=“app”>被保存到了src/App.vue文件中
c. new Vue({ … })被保存到了src/main.js中
d. 但是,将来运行时,三部分会被自动合并为一个唯一完整的HTML文件运行。
(2). 页面组件:
a. 所有页面组件也在src/views/文件夹下
b. 但是每个页面都变成一个.vue文件,而不是.js文件。
c. 什么是.vue文件: 专门在脚手架中集中保存一个组件的HTML+CSS+JS内容的特殊文件。
d. 为什么: .vue文件中专门设置了专门的区域分别编写HTML、CSS和js。既有提示,又不会互相干扰
e. 如何: 每个.vue文件都包含三个独立的区域:
1).<template>中放这个组件的HTML内容。必须唯一父元素包裹!
2). <script>中放一个组件js对象:{ … }
但是,脚手架采用模块化开发,所以组件js对象,必须使用export default {}抛出。
3). <style>中放这个组件所需的所有css代码
(3). 路由器对象: src/router/index.js
还是两部分: 路由字典routes和路由器对象router
(4). 组件: src/components/文件夹下
每个组件也都是一个.vue文件,也都包含三部分。 -
ES6模块化开发:
(1). 问题: 旧HTML和js关系中,所有js文件都必须先引入HTML文件中,再分配给其它需要的地方使用。非常不直观!极其混乱。
(2). 解决: 模块化开发,自由引入,不需要经过任何第三方.
(3). 如何: 2步:
a. 抛出模块对象:
1). 一个不带js代码的.vue文件,默认就是一个模块对象。可以被其它模块引入
2)… 如果一个文件中带有js代码,则必须用export default {}方式抛出对象才行。
b. 引入模块对象:
import 别名 from “模块对象所在文件的相对路径”
华安 唐伯虎
c. 结果: 引入后,在当前模块内就可使用其它模块对象中的成员。 -
示例: 封装页头组件:
(1). 在src/components/文件夹下新建MyHeader.vue
(2). 将App.vue中<div id=“nav”>…</div>剪切到了MyHeader.vue中<template>标签之内
(3). 将App.vue中<style>内所有#nav开头的选择器都剪切到了MyHeader.vue中<style>标签之内
(4). 在main.js中:
import MyHeader from “./components/MyHeader.vue”
Vue.component(“my-header”, MyHeader)
(5). 在App.vue中<div id=“app”>内第一行:
<my-header></my-header>
(6). 热编译/热重载 hot reload
a. 什么是: 一旦修改了原代码,ctrl+s保存后,框架会自动重新编译源代码。不用自己反复npm run serve。
b. 所以,今后,和可能早上上班来npm run serve一次,一天都不用重新npm run serve了!
c. 但是,个别操作或者发现缓存时,还是需要重新npm run serve的。 -
避免组件间样式冲突:
(1). 问题: 不同组件中的相同css选择器,最终运行时,会互相覆盖
(2). 原因: 其实,所有组件的css代码,最终都会编译为一个巨大的css文件,存储在一起。
(3). 解决: 2种:
a. 偷懒的/不总是有效:
1). 只要在<style>标签上添加scoped属性即可!
2). 原理:
i. 自动给每个组件中的所有元素都添加了自定义扩展属性,起了名称。
ii. 同一组件内的所有元素的自定义属性名保持一致。不同组件的元素,自定义属性名不同!
iii. 又自动给每个组件内的css选择器添加了同名的属性选择器。相同组件内的属性选择器只与当前组件内的元素上的自定义属性名相同。
iv. 结果因为每个组件内的元素和css属性选择器各不相同,就将不同组件的元素和选择器区分出来。
3). 问题:
i. 效率低: 要为每个元素都添加自定义属性,工作量大!
ii. 对外部引入的css文件,无效的!因为scoped只能给当前组件文件内的元素和css选择器添加自定义属性。无权给外部文件添加自定义属性。
b. 好的/总是有效: 2步:
1). 每创建一个组件,都要给这个组件的唯一父元素添加唯一的class名。且class名最好和组件名相同!
2). 凡是这个组件的选择器,一律以这个组件唯一父元素的class选择器作为开头!
十七. 懒加载
- 问题: 默认脚手架会将所有页面组件的HTML+CSS+JS,打包编译为一个巨大的app.js文件,在首次加载页面时,一次性下载到客户端。——首屏加载极慢,且没必要
- 解决: 懒加载: 只优先加载首屏需要的内容,其它页面组件的内容按需下载。
- 如何: 2种
(1). 异步延迟加载
a. 思想:
1). 不要把所有页面组件都打包在一个app.js文件中,应该每个页面组件单独打包成一个js文件。才有可能分头下载!
2). 在页面加载时,优先下载并显示首页组件的内容。其余页面组件的内容采用异步的方式在底层悄悄下载。不影响首屏加载速度!
b. 如何: 2步: 都在src/router/index.js中
1). 不要在开头,过早的引入其它页面组件的模块对象
只要import引入,就会被打包进app.js中。
2). 路由字典项必须改为;
{
path:"/details",
component:
()=>import(
/webpackChunkName: “自定义文件名”/
“…/views/页面组件.vue”
)
}
3). 结果:
i. 因为没有提前import Details,所以在app.js中不包含其它页面组件的内容,app.js变小不少
ii. 因为在路由字典项中配置了箭头函数引入其它页面组件,则其它页面组件被分别打包进独立的js文件中,底层自动分头异步下载。不影响首屏加载。
c. 问题: 即使用户不想看其它页面的内容,底层也会悄悄下载其它页面的内容。——偷跑流量
(2). 彻底懒加载
a. 思想: 在第一种情况异步延迟加载的基础上,进一步配置,不要提前异步延迟下载其它页面组件的内容。只有用户希望看哪个页面时,才临时下载这个页面组件的内容。
b. 原理:
1). 为什么第一种情况可以异步延迟加载其它页面组件:
i. vue脚手架打包之后,所有import引入的组件,都被打包进app.js中。又因为app.js在HTML中引入方式为preload,表示立刻下载,并立刻显示!着急!所以app.js是首屏优先加载。
ii. 其它不是用import引入的组件,在HTML中都被标记为prefetch,意为只在底层异步获得,暂时不用加载显示!不着急!
2). 如果我们可以删除prefetch的引入项目,就不用首次加载时就下载这些组件了!
c. 如何: 2步
1). 依然必须执行异步懒加载前两步!
2). 在脚手架项目的根目录新建vue.config.js文件,其中:
module.exports={
chainWebpack:config=>{
config.plugins.delete("prefetch")
//删除index.html开头的带有prefetch属性的link,不要异步下载暂时不需要的页面组件文件
},
}
强调: 因为vue.config.js是修改项目的配置信息,所以改完必须重启项目npm run serve 才能生效!
十八. HTTP代理方式跨域
- 问题: 旧的跨域方式,无论是CORS还是JSONP,都必须依靠后端工程师配置才能完成。
- 解决: HTTP代理方式跨域——纯前端的跨域方式,无需后端人员配合。
- 如何:
(1). 在vue.config.js文件中配置开发服务器/代理服务器
module.exports={
...懒加载配置... ,
devServer: {
proxy: {
'/': { //今后,当前项目中凡是以/开头的请求都归当前代理服务器代为发送。
target: `http://服务器端接口地址统一前缀部分`,
changeOrigin: true //允许跨域
}
}
}
}
(2). 注释掉main.js中的axios.defaults.baseURL=…
因为baseURL已经集中配置在代理服务器的target属性中
(3). 当发送axios请求时,用法和之前完全一样!只不过路径必须是"/相对路径"
十九.插槽
1. 什么是插槽
问题:程序中: 多个提示框,布局风格一致,只不过内部的具体内容不同
问题:现实中,早教机会为每种不同的卡片分别做一个读卡机吗?
解决:现实中,造一个带插槽的读卡器
程序中: 也可以造一个带样式和插槽的外壳
然后定义每个对话框时,都把这个外壳套上,再在外壳内填入每个对话框不同的内容
然后定义每个对话框时,都把这个外壳套上,再在外壳内填入每个对话框不同的内容
什么是插槽:
•插槽是组件中一块可动态改变HTML片段内容的区域
•今后,只要发现多个组件,拥有相同的结构框架,只是内部HTML片段有不同时,都应该使用插槽
2. 如何使用插槽
1. 先定义一个组件包含统一的外壳结构
–新建一个外壳组件,将任意一个现有对话框代码整体复制到组件中
–删除组件中将来可能发生变化的大片HTML片段位置,并用
<slot>标签占位
–组件中个别可能变化的文本,改为绑定语法
–并在props成员中添加外来属性
2. 再在每次使用组件时,动态改变组件中插槽区域的HTML片段内容
–2.1 在原对话框中引入带插槽的外壳组件
import Waike from “./Waike.vue”
export default {
… …,
components:{ Waike },
… …
}
–2.2 在最终组件上先添加外壳组件标签
–2.3 再在外壳组件内添加<template>,其中添加对话框独有内容
– 结果:
3.具名插槽
如果一个带插槽的外壳组件,有多个部位将来可能会改变,可以为每个部位指定插槽名.
将来使用插槽时,可以将不同的HTML片段,插入不同的插槽中
如果一个带插槽的外壳组件包含多个具名插槽,则使用时, 每个插槽对应的HTML片段,都要放在一个<template v- slot:插槽名>中
v-slot:插槽名,可简写为#插槽名
二十.Vuex
1.什么事Vuex
问题:如果组件结构很复杂,又要跨组件传值,使用父子传参,步骤会很繁琐
解决:找一个公共的位置存放多个组件都需要公用的数据
Vuex是专门帮助所有组件维护共享数据的公共区域。
今后,只要一个数据,有可能被多个组件使用,都可以通过Vuex来管理。
2.如何使用Vuex
1. 创建脚手架时,选择vuex选项,一起安装
结果: 在脚手架项目src文件夹下:
结果: 在main.js中:
2. 在store/index.js中state内添加要多个组件共享的变量
–state相当于商店货架上的商品,谁来商店都可访问
state: {
uid:-1,
uname:"dingding"
},
3. 在页面上,显示Vuex中的内容:
– 3.1 在页面组件export default {}之前引入Vuex提供的专门获取
Vuex中State中所有变量的函数mapState
import { mapState } from "vuex"
– 3.2 在页面组件export default {}中computed中调用mapState
函数,指定要解构出来的State中的变量名,当做当前组件的计
算属性使用
– 3.3 在页面中就可以绑定mapState帮我们生成的计算属性了
– 页面中: Welcome {{uname}}
4. 在Vuex中Mutations中定义专门修改State中变量的修改函数
5. 在页面上登录组件中使用setUname修改用户名,并切换页面中用户登录状态,同时显示新用户名
5.1 在登录组件export default {}之前引入Vuex提供的专门提取Vuex中所有State变量和Mutations方法的函数: mapState和mapMutations
import { mapMutations } from “vuex”
5.2 在登录组件export default {}中computed中解构出
mapState中包含的Vuex中的变量,当做当前组件的计算属性
5.3 在登录组件<template #success>中:<h3>Welcome{{uname}}</h3>
5.4在登录组件methods中,调用mapMutation()函数,并解构出修改uname变量要使用的方法。
5.5在登录组件methods中,登录方法login()中。setUname(this.form.uname)
6. Vuex中定义发送ajax请求的方法,专门发送的登录请求
6.1 在Vuex中,顶部提前引入axios:
import axios from ‘axios’
6.2 在Vuex中,Actions内添加vlogin函数
6.3 vlogin函数内发送axios请求,并接受返回值。
6.4 如果返回登录成功的结果,则:调用当前vuex大环境中的setUid()和setUname()两个函数。将服务器返回的uid和uname,两个值设置到State中的uid和uname两个属性上。
6.5 如果返回登录失败的结果,则:直接调用alert,提示登录失.
7. Vuex中定义发送ajax请求的方法,专门发送的登录请求
7.1 在登录组件中,引入Vuex提供的专门提取Actions中方法的mapActions函数
import { mapActions } from “vuex”
7.2 在登录组件中,methods中,调用mapActions函数,解构出发送ajax请求的函数vlogin
7.3 在登录组件中login方法中调用vlogin方法,如果登录成功,
就把success改为true,让登录框切换显示登录成功