更新日期:2021-02-10 晚 【新年快乐】
附:Vue学习随笔+商城项目【下】
目录(部分)
(一)ES6补充
1.1块级作用域
ES6之前没有块级作用域,ES5的var没有块级作用域的概念,只有function有作用域的概念,ES6的let、const引入了块级作用域。
ES5之前if和for都没有作用域,所以很多时候需要使用function的作用域,比如闭包。
1.1.1 什么是变量作用域
变量在什么范围内可用,类似Java的全局变量和局部变量的概念,全局变量,全局都可用,局部变量只在范围内可用。ES5之前的var是没有块级作用域的概念,使用var声明的变量就是全局的。
{
var name = 'zzz';
console.log(name);
}
console.log(name);
上述代码中{}外的console.log(name)
可以获取到name值并打印出来,用var声明赋值的变量是全局变量,没有块级作用域。
1.1.2 没有块级作用域造成的问题
if块级
var func;
if(true){
var name = 'zzz';
func = function (){
console.log(name);
}
func();
}
name = 'ttt';
func();
console.log(name);
代码输出结果为'zzz','ttt','ttt'
,第一次调用func(),此时name=‘zzz’,在if块外将name置成‘ttt’,此时生效了,if没有块级作用域。
for块级
定义五个按钮,增加事件,点击哪个按钮打印“第哪个按钮被点击了”。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>块级作用域</title>
</head>
<body>
<button>按钮1</button>
<button>按钮2</button>
<button>按钮3</button>
<button>按钮4</button>
<button>按钮5</button>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"> </script>
<script>
// 3.没有块级作用域引起的问题:for块级
var btns = document.getElementsByTagName("button");
for (var i = 0; i < btns.length; i++) {
btns[i].addEventListener('click',function (param) {
console.log("第"+i+"个按钮被点击了");
});
}
</script>
</body>
</html>
for块级中使用var
声明变量i时,是全局变量,点击任意按钮结果都是“第五个按钮被点击了”。
即每一次看似在for块级里面声明的 i ,其实都是全局作用域的 i ,循环到最后一次,前面的i被覆盖,于是全局作用域 i=5 ,不管点击任何按钮都是最后一个。
说明在执行btns[i].addEventListener('click',function())
时,for块级循环已经走完,此时i=5
,所有添加的事件的i都是5。
改造上述代码,将for循环改造,由于函数有作用域,使用闭包能解决上述问题。
// 使用闭包,函数有作用域
for (var i = 0; i < btns.length; i++) {
(function (i) {
btns[i].addEventListener('click',function (param) {
console.log("第"+i+"个按钮被点击了");
})
})(i);
}
结果如图所示,借用函数的作用域解决块级作用域的问题,因为有块级作用域,每次添加的i都是当前i。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WthpEdO4-1612961537024)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\1.1.2-1.png)]
在ES6中使用let/const解决块级作用域问题,let和const有块级作用域,const定义常量,在for块级中使用let解决块级作用域问题。
// ES6使用let/const
const btns = document.getElementsByTagName("button");
for (let i = 0; i < btns.length; i++) {
btns[i].addEventListener('click',function (param) {
console.log("第"+i+"个按钮被点击了");
})
}
结果和使用闭包解决一致。
1.2 const的使用
- const用来定义常量,赋值后不能再赋值,再次赋值会报错。
<script>
//1.定义常量,赋值后不能再赋值,在赋值报错
const count = 1
// count = 2
</script>
- const不能只声明不赋值,会报错。
<script>
//2.只声明不赋值,必须赋值
// const count;
</script>
- const常量含义是你不能改变其指向的对象,例如user,但是你可以改变user(对象内部的)属性。
<script>
//3.常量的含义是你不能改变其指向的对象user,但是你可以改变user属性
const user = {
name:"zzz",
age:24,
height:175
}
console.log(user)
user.name = "ttt"
user.age = 22
user.height = 188
console.log(user)
</script>
- const内存地址详解
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-49PrUvHv-1612961537036)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\1.2-1.png)]
对象count一开始只想0x10的地址,直接将count(给count重新赋值,指向一个新的对象)指向地址改为0x20会报错,const是常量,无法更改对象地址。
对象user一开始指向0x10地址,user有Name
、Age
、Height
三个属性,此时修改属性Name='ttt'
,user对象的地址未改变,不会报错。
1.3 ES6的增强写法
1.3.1 ES6的对象属性增强型写法
- ES6以前定义一个对象
const name = "zzz";
const age = 18;
const user = {
name:name,
age:age
}
console.log(user);
- ES6写法
const name = "zzz";
const age = 18;
const user = {
name,age
}
console.log(user);
1.3.2 ES6对象的函数增强型写法
- ES6之前对象内定义函数
const obj = {
run:function(){
console.log("奔跑");
}
}
- ES6写法
const obj = {
run(){
console.log("奔跑");
}
}
1.4 箭头函数
认识箭头函数
传统定义函数的方式
const aaa = function (param) {
}
对象字面量中定义函数
const obj = {
bbb (param) { },
}
ES6中的箭头函数
//const ccc = (参数列表) => {}
const ccc = () => {}
箭头函数的参数和返回值
参数问题
1.放入两个参数
const sum = (num1,num2) => {
return num1 + num2
}
2.放入一个参数,()可以省略
const power = num => {
return num * num
}
函数内部
1.函数中代码块中有多行代码
const test = () =>{
console.log("hello zzz")
console.log("hello vue")
}
2.函数代码块中只有一行代码,可以省略return
// const mul = (num1,num2) => {
// return num1 * num2
// }
const mul = (num1,num2) => num1* num2
// const log = () => {
// console.log("log")
// }
const log = () => console.log("log")
箭头函数的this使用
什么时候使用箭头函数
- 当把一个函数作为参数传入另外一个函数时使用
setTimeout(function () {
console.log(this)
} , 1000);
setTimeout(() => {
console.log(this) // 这里this找的是window的this
}, 1000);
结论:箭头函数没有this,这里this引用的是最近作用域(aaa函数里的this)的this。
const obj = {
aaa(){
setTimeout(function () {
console.log(this) // window
});
setTimeout(() => {
console.log(this) // obj
});
}
}
obj.aaa()
上述中第一个是window对象的this,第二个箭头函数的this是obj的。
const obj = {
aaa() {
setTimeout(function () {
setTimeout(function () {
console.log(this) //window
})
setTimeout(() => {
console.log(this) //window
})
})
setTimeout(() => {
setTimeout(function () {
console.log(this) //window
})
setTimeout(() => {
console.log(this) //obj
})
})
}
}
obj.aaa()
高阶函数
1.5.1 filter过滤函数
-
只能用来过滤,不能处理元素。
-
返回值:true/false,返回原数组里的元素
const nums = [2,3,5,1,77,55,100,200]
//要求获取nums中大于50的数
//回调函数会遍历nums中每一个数,传入回调函数,在回调函数中写判断逻辑,返回true则会被数组接收,false会被拒绝
let newNums = nums.filter(function ( num ) {
if(num > 50){
return true;
}
return false;
})
//可以使用箭头函数简写
// let newNums = nums.filter(num => num >50)
1.5.2 map高阶函数
-
可以对元素进行处理
-
返回值:回调的return
// 要求将已经过滤的新数组每项乘以2
//map函数同样会遍历数组每一项,传入回调函数为参数,num是map遍历的每一项,回调函数function返回值会被添加到新数组中
let newNums2 = newNums.map(function (num) {
return num * 2
})
//简写
// let newNums2 = newNums.map(num => num * 2)
console.log(newNums2);
1.5.3 reduce高阶函数
- 对数组元素汇总
- 参数
- 回调函数
- preValue:初始值,0
- item:遍历数组元素值
- 0
- 回调函数
// 3.reduce高阶函数
//要求将newNums2的数组所有数累加
//reduce函数同样会遍历数组每一项,传入回调函数和‘0’为参数,0表示回调函数中preValue初始值为0,回调函数中参数preValue是每一次回调函数function返回的值,currentValue是当前值
//例如数组为[154, 110, 200, 400],则回调函数第一次返回值为0+154=154,第二次preValue为154,返回值为154+110=264,以此类推直到遍历完成
let newNum = newNums2.reduce(function (preValue,currentValue) {
return preValue + currentValue
},0)
//简写
// let newNum = newNums2.reduce((preValue,currentValue) => preValue + currentValue)
console.log(newNum);
1.5.4综合使用
//三个需求综合
let n = nums.filter(num => num > 50).map(num => num * 2).reduce((preValue,currentValue) => preValue + currentValue)
console.log(n);
(二)HelloVue
2.1 HelloVuejs
2.1.0 安装
-
cdn
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
-
下载
-
npm
npm i -S vue
2.1.1 命令式编程
vue是声明式编程,区别于jquery的命令式编程。
原生js做法(命令式编程)
- 创建div元素,设置id属性
- 定义一个变量叫message
- 将message变量放在div元素中显示
- 修改message数据
- 将修改的元素替换到div
2.1.2 声明式编程
vue写法(声明式)
- 创建一个div元素,设置id属性
- 定义一个vue对象,将div挂载在vue对象上
- 在vue对象内定义变量message,并绑定数据
- 将message变量放在div元素上显示
- 修改vue对象中的变量message,div元素数据自动改变
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<title>HelloVuejs</title>
</head>
<body>
<div id="app">
<h2>{{message}}</h2>
<p>{{name}}</p>
</div>
<script>
//let变量/const常量
//编程范式:声明式编程
const app = new Vue({
el:"#app",//用于挂载要管理的元素
data:{//定义数据
message:"HelloVuejs",
name:"zzz"
}
})
</script>
</body>
</html>
在谷歌浏览器中按F12,在开发者模式中console控制台,改变vue对象的message值,页面显示也随之改变。
{{message}}
表示将变量message输出到标签h2中,所有的vue语法都必须在vue对象挂载的div元素中,如果在div元素外使用是不生效的。el:"#app"
表示将id为app的div挂载在vue对象上,data表示变量对象。
2.2 vue列表的展示(v-for)
开发中常用的数组有许多数据,需要全部展示或者部分展示,在原生JS中需要使用for循环遍历依次替换div元素,在vue中,使用v-for
可以简单遍历生成元素节点。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<title>vue列表展示</title>
</head>
<body>
<div id="app">
<h2>{{message}}</h2>
<ul>
<li v-for="(item, index) in movies" :key="index">{{item}}</li>
</ul>
</div>
<script>
const app = new Vue({
el:"#app",//用于挂载要管理的元素
data:{//定义数据
message:"你好啊",
movies:["星际穿越","海王","大话西游","复仇者联盟"]//定义一个数组
}
})
</script>
</body>
</html>
显示结果如图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GBjLxKSK-1612961537038)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\2.2.1-1.png)]
<li v-for="(item, index) in movies" :key="index">{{item}}</li>
item表示当前遍历的元素,index表示元素索引, 为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key
属性。建议尽可能在使用 v-for
时提供 key
attribute,除非遍历输出的 DOM 内容非常简单,或者是刻意依赖默认行为以获取性能上的提升。
因为它是 Vue 识别节点的一个通用机制,key
并不仅与 v-for
特别关联。
不要使用对象或数组之类的非基本类型值作为
v-for
的key
。请用字符串或数值类型的值。
2.3 vue案例-计数器
使用vue实现一个小计数器,点击+
按钮,计数器+1,使用-
按钮计数器-1。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<title>vue计数器</title>
</head>
<body>
<div id="app">
<h2>当前计数:{{count}}</h2>
<!-- <button v-on:click="count--">-</button>
<button v-on:click="count++">+</button> -->
<button v-on:click="sub()">-</button>
<button @click="add()">+</button>
</div>
<script>
const app = new Vue({
el:"#app",//用于挂载要管理的元素
data:{//定义数据
count:0
},
methods: {
add:function(){
console.log("add")
this.count++
},
sub:function(){
console.log("sub")
this.count--
}
},
})
</script>
</body>
</html>
-
定义vue对象并初始化一个变量count=0
-
定义两个方法
add
和sub
,用于对count++或者count– -
定义两个button对象,给button添加上点击事件
在vue对象中使用methods表示方法集合,使用
v-on:click
的关键字给元素绑定监听点击事件,给按钮分别绑定上点击事件,并绑定触发事件后回调函数add
和sub
。也可以在回调方法中直接使用表达式。例如:count++
和count--
。
2.4 Vue中的MVVM
-
MVVM:https://zh.wikipedia.org/wiki/MVVM
-
View ViewModel Model DOM DOM Listeners & Data Bindings Plain Js Objects new Vue()
2.5 创建Vue时, options可以放哪些东西
-
el: string / HTMLElement
-
data: Object / Function
- 注:在组件中,data必须是函数(Function)
-
methods: { [ key: string ]: Function }
-
生命周期函数
(三)插值操作
3.1 Mustache语法
3.1.1
{{}}:mustache(胡须)
因为{{}}
像胡须,又叫大括号语法。
3.1.2
在vue对象挂载的dom元素中,{{}}
不仅可以直接写变量,还可以写简单表达式。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Mustache的语法</title>
</head>
<body>
<div id="app">
<h2>{{message}}</h2>
<h2>{{message}},啧啧啧</h2>
<!-- Mustache的语法不仅可以直接写变量,还可以写简单表达式 -->
<h2>{{firstName + lastName}}</h2>
<h2>{{firstName + " " + lastName}}</h2>
<h2>{{firstName}}{{lastName}}</h2>
<h2>{{count * 2}}</h2>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
message:"你好啊",
firstName:"skt t1",
lastName:"faker",
count:100
}
})
</script>
</body>
</html>
3.2 v-once
v-once表示该dom元素只渲染一次,之后数据改变,不会再次渲染。
<div id="app">
<h2>{{message}}</h2>
<!-- 只会渲染一次,数据改变不会再次渲染 -->
<h2 v-once>{{message}}</h2>
</div>
上述{{message}}
的message修改后,第一个h2标签数据会自动改变,第二个h2不会。
3.3 v-html
在某些时候我们不希望直接输出<a href='http://www.baidu.com'>百度一下</a>
这样的字符串,而输出被html自己转化的超链接。此时可以使用v-html渲染成页面
区别:{{ url }} 与 v-html=‘url’ 作用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>v-html指令的使用</title>
</head>
<body>
<div id="app">
<h2>不使用v-html</h2>
<h2>{{url}}</h2>
<h2>使用v-html,直接插入html</h2>
<h2 v-html="url"></h2>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
message:"你好啊",
url:"<a href='http://www.baidu.com'>百度一下</a>"
}
})
</script>
</body>
</html>
输出结果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AhBWboJR-1612961537041)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\3.3-1.png)]
3.4 v-text
v-text会覆盖dom元素中的数据,相当于js的 innerHTML 方法。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>v-text指令的使用</title>
</head>
<body>
<div id="app">
<h2>不使用v-text</h2>
<h2>{{message}},啧啧啧</h2>
<h2>使用v-text,以文本形式显示,会覆盖</h2>
<h2 v-text="message">,啧啧啧</h2>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
message:"你好啊"
}
})
</script>
</body>
</html>
如图所示,使用{{message}}
是拼接变量和字符串,而是用v-text是直接覆盖字符串内容。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lQRQIIMQ-1612961537042)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\3.4-1.png)]
3.5 v-pre
有时候我们期望**直接输出{{ message }}
**这样的字符串,而不是被{{ }}
语法转化的message的变量值,此时我们可以使用v-pre
标签。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>v-pre指令的使用</title>
</head>
<body>
<div id="app">
<h2>不使用v-pre</h2>
<h2>{{message}}</h2>
<h2>使用v-pre,不会解析</h2>
<h2 v-pre>{{message}}</h2>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
message:"你好啊"
}
})
</script>
</body>
</html>
结果如图,使用v-pre修饰的dom会直接输出字符串。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l6QkaBcj-1612961537044)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\3.5-1.png)]
3.6 v-cloak
有时候因为加载延时问题,例如卡掉了,数据没有及时刷新,就造成了页面显示从{{ message }}
到message变量“你好啊”的变化,这样闪动的变化,会造成用户体验不好。此时需要使用到v-cloak
的这个标签。在vue解析之前,div属性中有v-cloak
这个标签,在vue解析完成之后,v-cloak标签被移除。
解析前:display:none;
加载解析后:display:block
,元素显示出来。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>v-cloak指令的使用</title>
<style>
[v-cloak]{
display: none;
}
</style>
</head>
<body>
<div id="app" v-cloak>
<h2>{{message}}</h2>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
//在vue解析前,div中有一个属性cloak
//在vue解析之后,div中没有一个属性v-cloak
setTimeout(() => {
const app = new Vue({
el: "#app",
data: {
message: "你好啊"
}
})
}, 1000);
</script>
</body>
</html>
这里通过延时1秒模拟加载卡住的状态,结果一开始不显示message的值,div元素中有v-cloak的属性,1秒后显示message变量的值,div中的v-cloak元素被移除。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WqpKrjQ7-1612961537045)(./images/3.6-1.gif)]
(四)动态绑定属性
4.1 v-bind的基本使用
某些时候我们并不想将变量放在标签内容中,像这样<h2>{{message}}</h2>
是将变量h2标签括起来,类似js的innerHTML。
但是我们期望将变量imgURL
写在如下位置,想这样<img src="imgURL" alt="">
导入图片是希望动态获取图片的链接,此时的imgURL并非变量而是字符串imgURL,如果要将其生效为变量,需要使用到一个标签v-bind:
,
像这样<img v-bind:src="imgURL" alt="">
,而且这里也不能使用Mustache语法,类似<img v-bind:src="{{imgURL}}" alt="">
,这也是错误的。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>v-bind的基本使用</title>
</head>
<body>
<div id="app">
<!-- 错误的做法这里不能使用Mustache语法 -->
<!-- <img v-bind:src="{{imgURL}}" alt=""> -->
<!-- 正确的做法使用v-bind指令 -->
<img v-bind:src="imgURL" alt="">
<a v-bind:href="aHerf"></a>
<!-- 语法糖写法 -->
<img :src="imgURL" alt="">
<a :href="aHerf"></a>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
message:"你好啊",
imgURL:"https://cn.bing.com/th?id=OIP.NaSKiHPRcquisK2EehUI3gHaE8&pid=Api&rs=1",
aHerf:"http://www.baidu.com"
}
})
</script>
</body>
</html>
此时vue对象中定义的imgURL
变量和aHerf
变量可以动态的绑定到img标签的src属性和a标签的href属性。v-bind:
由于用的很多,vue对他有一个语法糖的优化写法也就是**:
**,此时修改imgURL变量图片叶重新加载。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I1yVAjQf-1612961537046)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\4.1-1.gif)]
4.2 v-bind动态绑定class(对象语法)
有时候我们期望对Dom元素的节点的class进行动态绑定,选择此Dom是否有指定class属性。例如,给h2标签加上class="active"
,当Dom元素有次class时候,变红<style>.active{color:red;}</style>
,在写一个按钮绑定事件,点击变黑色,再次点击变红色。
动态绑定class对象语法:
<h2 v-bind:class="{类名1: true, 类名2: boolean}">{{message}}</h2>
当布尔值为 true 时,“类1生效”;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>v-bind动态绑定class(对象语法)</title>
<style>
.active{
color:red;
}
</style>
</head>
<body>
<div id="app">
<!-- <h2 class="active">{{message}}</h2>
<h2 :class="active">{{message}}</h2> -->
<!-- 动态绑定class对象用法 -->
<!-- <h2 :class="{key1:value1,key2:value2}">{{message}}</h2>
<h2 :class="{类名1:true,类名2:boolean}">{{message}}</h2> -->
<h2 class="title" :class="{active:isActive}">{{message}}</h2>
<h2 class="title" :class="getClasses()">{{message}}</h2>
<button @click="change">点击变色</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
message:"你好啊",
active:"active",
isActive:true
},
methods: {
change(){
this.isActive = !this.isActive
},
getClasses(){
return {active:this.isActive}
}
},
})
</script>
</body>
</html>
定义两个变量active
和isActive
,在Dom元素中使用:class={active:isActive}
,此时绑定的class='active'
,isActive为true,active显示,定义方法change()绑定在按钮上,点击按钮this.isActive = !this.isActive
,控制Dom元素是否有class='active'
的属性。
4.3 v-bind动态绑定class(数组用法)
class属性中可以放数组,会依次解析成对应的class。
语法
{{message}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>v-bind动态绑定class(数组用法)</title>
<style>
</style>
</head>
<body>
<div id="app">
<!-- 加上单引号当成字符串 -->
<h2 class="title" :class="['active','line']">{{message}}</h2>
<!-- 不加会被当成变量 -->
<h2 class="title" :class="[active,line]">{{message}}</h2>
<h2 class="title" :class="getClasses()">{{message}}</h2>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
message:"你好啊",
active:"aaaa",
line:'bbbb'
},
methods: {
getClasses(){
return [this.active,this.line]
}
},
})
</script>
</body>
</html>
-
加上单引号的表示字符串
-
不加的会当成变量
-
可以直接使用方法返回数组对象
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lAymuX7l-1612961537046)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\4.3-2.png)]
4.4 v-for和v-bind结合
使用v-for和v-bind实现一个小demo,将电影列表展示,并点击某一个电影列表时候,将此电影列表变成红色。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>作业(v-for和v-bind的结合)</title>
<style>
.active{
color:red;
}
</style>
</head>
<body>
<div id="app">
<ul>
<li v-for="(item, index) in movies" :key="index" :class="{active:index===currentIndex}" @click="changeColor(index)" >{{index+"---"+item}}</li>
</ul>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
currentIndex:0,
movies:["海王","海贼王","火影忍者","复仇者联盟"]
},
methods: {
changeColor(index){
this.currentIndex = index
}
},
})
</script>
</body>
</html>
v-for时候的index索引,给每行绑定事件点击事件,点击当行是获取此行索引index并赋值给currentIndex
,使用v-bind:
绑定class,当index===currentIndex
Dom元素有active的class,颜色变红。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v1Dx6rhV-1612961537048)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\4.4-1.gif)]
4.5 v-bind动态绑定style
4.5.1 v-bind动态绑定style(对象语法)
<!-- <h2 :style="{key(属性名):value(属性值)}">{{message}}</h2> -->
<!-- 加单引号,当成字符串解析 -->
<h2 :style="{fontSize:'50px'}">{{message}}</h2>
'50px'必须加上单引号, 否则是当做一个变量去解析
<!-- 不加单引号,变量解析 -->
<h2 :style="{fontSize:fontSize}">{{message}}</h2>
<h2 :style="getStyle()">{{message}}</h2>
4.5.2 v-bind动态绑定style(数组语法)
<div id="app">
<h2 :style="[baseStyle]">{{message}}</h2>
<h2 :style="getStyle()">{{message}}</h2>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
message:"你好啊",
baseStyle:{backgroundColor:'red'}
},
methods: {
getStyle(){
return [this.baseStyle]
}
},
})
</script>
类似绑定class,绑定style也是一样的。
(五)计算属性
5.1 计算属性的基本使用
现在有变量姓氏和名字,要得到完整的名字。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>计算属性的基本使用</title>
</head>
<body>
<div id="app">
<!-- Mastache语法 -->
<h2>{{firstName+ " " + lastName}}</h2>
<!-- 方法 -->
<h2>{{getFullName()}}</h2>
<!-- 计算属性 -->
<h2>{{fullName}}</h2>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
firstName:"skt t1",
lastName:"faker"
},
computed: {
fullName:function(){
return this.firstName + " " + this.lastName
}
},
methods: {
getFullName(){
return this.firstName + " " + this.lastName
}
},
})
</script>
</body>
</html>
- 使用Mastache语法拼接
<h2>{{firstName+ " " + lastName}}</h2>
- 使用方法methods
<h2>{{getFullName()}}</h2>
- 使用计算属性computed
<h2>{{fullName}}</h2>
例子中计算属性computed看起来和方法似乎一样,只是方法调用需要使用(),而计算属性不用,方法取名字一般是动词见名知义,而计算属性是属性是名词,但这只是基本使用。
5.2 计算属性的复杂使用
现在有一个数组数据books,里面包含许多book对象,数据结构如下:
books:[
{id:110,name:"JavaScript从入门到入土",price:119},
{id:111,name:"Java从入门到放弃",price:80},
{id:112,name:"编码艺术",price:99},
{id:113,name:"代码大全",price:150},
]
要求计算出所有book的总价格totalPrice
。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>计算属性的复杂使用</title>
</head>
<body>
<div id="app">
<h2>总价格:{{totalPrice}}</h2>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
books:[
{id:110,name:"JavaScript从入门到入土",price:119},
{id:111,name:"Java从入门到放弃",price:80},
{id:112,name:"编码艺术",price:99},
{id:113,name:"代码大全",price:150},
]
},
computed: {
totalPrice(){
let result= 0;
for (let i = 0; i < this.books.length; i++) {
result += this.books[i].price;
}
return result
}
}
})
</script>
</body>
</html>
获取每一个book对象的price累加,当其中一个book的价格发生改变时候,总价会随之变化。
5.3 计算属性的setter和getter
在计算属性中其实是由这样两个方法setter和getter。
computed: {
fullName:{
//计算属性一般没有set方法,只读属性
set:function(newValue){
console.log("-----")
const names = newValue.split(" ")
this.firstName = names[0]
this.lastName = names[1]
},
get:function(){
return this.firstName + " " + this.lastName
}
}
}
但是计算属性一般没有set方法,只读属性,只有get方法,但是上述中newValue就是新的值,也可以使用set方法设置值,但是一般不用。
computed的getter/setter
请看如下代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Vue计算属性的getter和setter</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
</head>
<body>
<div id="app">
<h1>计算属性:computed的getter/setter</h1>
<h2>fullName</h2>
{{fullName}}
<h2>firstName</h2>
{{firstName}}
<h2>lastName</h2>
{{lastName}}
</div>
<script>
var app = new Vue({
el:"#app",
data:{
firstName:"zhang",
lastName:"san",
},
computed: {
fullName:{
get:function(){
return this.firstName+" "+this.lastName
},
set:function(value){
var list = value.split(' ');
this.firstName=list[0]
this.lastName=list[1]
}
}
},
});
</script>
</body>
</html>
初始化
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qOJwZYs2-1612961537049)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\6.png)]
修改fullName*
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9HAkmHVp-1612961537050)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\7.png)]
结论
- 通过这种方式,我们可以在改变计算属性值的同时也改变和计算属性相关联的属性值。
5.4 计算属性和methods的对比
直接看代码,分别使用计算属性和方法获得fullName的值。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>计算属性和methods的对比</title>
</head>
<body>
<div id="app">
<!-- methods,即使firstName和lastName没有改变,也需要再次执行 -->
<h2>{{getFullName}}</h2>
<h2>{{getFullName}}</h2>
<h2>{{getFullName}}</h2>
<h2>{{getFullName}}</h2>
<!-- 计算属性有缓存,只有关联属性改变才会再次计算 -->
<h2>{{fullName}}</h2>
<h2>{{fullName}}</h2>
<h2>{{fullName}}</h2>
<h2>{{fullName}}</h2>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
firstName:"skt t1",
lastName:"faker"
},
computed: {
fullName(){
console.log("调用了计算属性fullName");
return this.firstName + " " + this.lastName
}
},
methods: {
getFullName(){
console.log("调用了getFullName");
return this.firstName + " " + this.lastName
}
},
})
</script>
</body>
</html>
分别使用方法和计算属性获取四次fullName,结果如图。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WamdMAtI-1612961537052)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\5.4-1.png)]
由此可见计算属性有缓存,在this.firstName + " " + this.lastName
的属性不变的情况下,methods调用了四次,而计算属性才调用了一次,性能上计算属性明显比methods好。而且在改动firstName的情况下,计算属性只调用一次,methods依然要调用4次。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NIzPUdV2-1612961537053)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\5.4-2.png)]
5.5 Vue计算属性与侦听器总结
照例看一段代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Vue计算属性/侦听器/方法比较</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
</head>
<body>
<div id="app">
<h1>计算属性:computed</h1>
{{fullName}}
<h1>方法:methods</h1>
{{fullName2()}}
<h1>侦听器:watch</h1>
{{watchFullName}}
<h1>年龄</h1>
{{age}}
</div>
<script>
var other = 'This is other';
var app = new Vue({
el:"#app",
data:{
firstName:"zhang",
lastName:"san",
watchFullName:"zhangsan",
age:18,
},
watch: {
firstName:function(newFirstName, oldFirstName){
console.log("firstName触发了watch,newFirstName="+newFirstName+",oldFirstName="+oldFirstName)
this.watchFullName = this.firstName+this.lastName+","+other
},
lastName:function(newLastName, oldLastName){
console.log("lastName触发了watch,newLastName="+newLastName+",oldLastName="+oldLastName)
this.watchFullName = this.firstName+this.lastName+","+other
}
},
computed: {
fullName:function(){
console.log("调用了fullName,计算了一次属性")
return this.firstName+this.lastName+","+other;
}
},
methods: {
fullName2:function(){
console.log("调用了fullName,执行了一次方法")
fullName2 = this.firstName+this.lastName+","+other;
return fullName2;
}
}
});
</script>
</body>
</html>
初始化:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1wayPWZw-1612961537054)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\1.png)]
修改firstName/lastName/两者都修改
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LTJ9LDOU-1612961537054)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\2.png)]
修改computed中没计算的age
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7xarxS6Z-1612961537056)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\3.png)]
修改Vue实例外的对象
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vXpZQgQ1-1612961537057)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\4.png)]
修改Vue实例外对象后在修改Vue实例内的对象
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N7cPCfMY-1612961537058)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\5.png)]
测试结论:
- 使用computed计算了fullName属性,值为firstName+lastName。计算属性具有
缓存功能
,当firstName和lastName都不改变的时候,fullName不会重新计算,比如我们改变age的值,fullName的值是不需要重新计算的。 - methods并没有缓存特性,比如我们改变age的值,fullName2()方法会被执行一遍。
- 当一个功能可以用上面三个方法来实现的时候,明显使用computed更合适,代码简单也有缓存特性。
- 计算属性范围在vue实例内,修改vue实例外部对象,不会重新计算渲染,但是如果先修改了vue实例外对象,在修改vue计算属性的对象,那么外部对象的值也会重新渲染。
计算属性:computed
计算属性范围在Vue实例的fullName内所管理的firstName和lastName,通常监听多个变量
侦听器:watch
监听数据变化,一般只监听一个变量或数组
使用场景
watch(异步场景
),computed(数据联动
)
(六)事件监听
6.1 v-on的基本使用
语法糖:
v-on:→ @
方法名可以不加括号。
在前面的计数器案例中使用了v-on:click
监听单击事件。这里在回顾一下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<title>Document</title>
</head>
<body>
<div id="app">
<h2>{{count}}</h2>
<!-- <button v-on:click="count++">加</button>
<button v-on:click="count--">减</button> -->
<button @click="increment">加</button>
<button @click="decrement()">减</button>
</div>
<script>
const app = new Vue({
el:"#app",
data:{
count:0
},
methods: {
increment(){
this.count++
},
decrement(){
this.count--
}
}
})
</script>
</body>
</html>
使用v-on:click
给button绑定监听事件以及回调函数,@是v-on:
的语法糖,也就是简写也可以使用**@click
**。方法一般是需要写方法名加上(),在@click
中可以省掉,如上述的<button @click="increment">加</button>
。
6.2 v-on的参数传递
了解了v-on的基本使用,现在需要了解参数传递。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app">
<!-- 事件没传参 -->
<button @click="btnClick">按钮1</button>
<button @click="btnClick()">按钮2</button>
<!-- 事件调用方法传参,写函数时候省略小括号,但是函数本身需要传递一个参数 -->
<button @click="btnClick2(123)">按钮3</button>
<button @click="btnClick2()">按钮4</button>
<button @click="btnClick2">按钮5</button>
<!-- 事件调用时候需要传入event还需要传入其他参数 -->
<button @click="btnClick3($event,123)">按钮6</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
methods:{
btnClick(){
console.log("点击XXX");
},
btnClick2(value){
console.log(value+"----------");
},
btnClick3(event,value){
console.log(event+"----------"+value);
}
}
})
</script>
</body>
</html>
-
事件没传参,可以省略()
-
事件调用方法传参了,写函数时候省略了小括号,但是函数本身是需要传递一个参数的,这个参数就是原生事件(如click)event参数传递进去
-
如果同时需要传入某个参数,同时需要event是,可以通过
$event
传入事件。- 按钮4调用
btnClick2(value){}
,此时undefined
。按钮5调用时省略了(),会自动传入原生event事件,如果我们需要event对象还需要传入其他参数,可以使用$event
对象。
- 按钮4调用
6.3 v-on的修饰词
.stop
btn的click事件不会传播,不会冒泡到上层,调用event.stopPropagation()
。
.prevent
调用event.preeventDefault
阻止默认行为。(return false)
.enter
只监听 ’ 回车 ‘ 键盘事件。
.once
只触发一次回调。
.native
监听组件根元素的原生事件
组件上绑定的事件必须加上.native才能监听到
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>v-on的修饰符</title>
</head>
<body>
<div id="app">
<!--1. .stop的使用,btn的click事件不会传播,不会冒泡到上层,调用event.stopPropagation() -->
<div @click="divClick">
<button @click.stop="btnClick">按钮1</button>
</div>
<!-- 2. .prevent 调用event.preeventDefault阻止默认行为 -->
<form action="www.baidu.com">
<button type="submit" @click.prevent="submitClick">提交</button>
</form>
<!--3. 监听键盘的事件 -->
<input type="text" @click.enter="keyup">
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
methods:{
btnClick(){
console.log("点击button");
},
divClick(){
console.log("点击div");
},
submitClcik(){
console.log("提交被阻止了")
},
keyup(){
console.log("keyup点击")
}
}
})
</script>
</body>
</html>
(七)条件判断
7.1 v-if、v-eles、v-else-if
- v-if用于条件判断,判断Dom元素是否显示。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app">
<h2 v-if="isFlag">isFlag为true显示这个</h2>
<h2 v-show="isShow">isShow为true是显示这个</h2>
<div v-if="age<18">小于18岁未成年</div>
<div v-else-if="age<60">大于18岁小于60岁正值壮年</div>
<div v-else="">大于60岁,暮年</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
isFlag:true,
isShow:false,
age:66
}
})
</script>
</body>
</html>
-
单独使用v-if,变量为布尔值,为true才渲染Dom
-
v-show的变量也是布尔值,为true才显示内容,类似css的display
-
v-if、v-else、v-else-if联合使用相当于if、elseif、else,但是在条件比较多的时候建议使用计算属性。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cY4oPGai-1612961537060)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\7.1-1.png)]
7.2 v-if的demo
- 在登录网站是经常可以选择使用账户名或者邮箱登录的切换按钮。要求点击按钮切换登录方式。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app">
<span v-if="isUser">
<label for="username">用户账号</label>
<input type="text" id="username" placeholder="请输入用户名" >
</span>
<span v-else="isUser">
<label for="email">用户邮箱</label>
<input type="text" id="email" placeholder="请输入用户邮箱" >
</span>
<button @click="isUser=!isUser">切换类型</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
isUser:true
}
})
</script>
</body>
</html>
使用v-if
和v-else
选择渲染指定的Dom,点击按钮对isUser
变量取反。
这里有个小问题,如果已经输入了账号了,此时想切换到邮箱输入,输入框未自己清空。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bJ6NObuu-1612961537061)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\7.2-1.gif)]
这里需要了解一下vue底层操作,此时input输入框值被复用了。
-
vue在进行DOM渲染时,出于性能考虑,会复用已经存在的元素,而不是每次都创建新的DOM元素。
-
在上面demo中,Vue内部发现原来的input元素不再使用,所以直接将其映射对应虚拟DOM,用来复用。
key的使用
- 如果不希望出现类似复用问题,可以给对应的dom元素加上
key
值,并保证key
不同。
<input type="text" id="username" placeholder="请输入用户名" key="username">
<input type="text" id="email" placeholder="请输入用户邮箱" key="email">
7.3 v-show
v-if 看似和 v-show 实现一样的效果,但是内部 v-show 只是用css将操作的元素隐藏显示,而v-if是新增和删除元素。v-show只是操作元素的style属性display,都没会被创建。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app">
<h2 v-show="isFlag">v-show只是操作元素的style属性display,都没会被创建</h2>
<h2 v-if="isFlag">v-if是新增和删除dom元素</h2>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
isFlag:true
}
})
</script>
</body>
</html>
- v-if: 当条件为false时, 包含v-if指令的元素, 根本就不会存在dom中
- v-show: 当条件为 false 时, v-show只是给我们的元素添加一个行内样式: display: none
(八)遍历循环
8.1 v-for遍历数组
语法
<li v-for="(item,index) in names" >{{index+":"+item}}</li>
- index表示索引,item表示当前遍历的元素
案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app">
<!-- 1.遍历过程没有使用索引(下标值) -->
<ul>
<li v-for="item in names" >{{item}}</li>
</ul>
<!-- 2.遍历过程有使用索引(下标值) -->
<ul>
<li v-for="(item,index) in names" >{{index+":"+item}}</li>
</ul>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
names:["zzz","ttt","yyy"]
}
})
</script>
</body>
</html>
一般需要使用索引值。
8.2 v-for遍历对象
参数
- 在遍历对象的过程中, 如果只是获取一个值, 那么获取到的是value
- 获取key和value 格式: (value, key)
- 获取key和value和index 格式: (value, key, index)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app">
<!-- 1.遍历过程没有使用index索引-->
<!-- 格式为:key-value -->
<ul>
<li v-for="(value,key) in user" >{{key+"-"+value}}</li>
</ul>
<!-- 格式为:key-value-index -->
<ul>
<li v-for="(value,key,index) in user" >{{key+"-"+value+"-"+index}}</li>
</ul>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
user:{
name:"zzz",
height:188,
age:24
}
}
})
</script>
</body>
</html>
- 遍历过程没有使用index索引,
<li v-for="(value,key) in user" >{{key+"-"+value}}</li>
,value表示当前元素是属性值,key表示user对象属性名。 - 遍历过程使用index索引,index表示索引从0开始。
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HDANE73u-1612961537062)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\8.2-1.png)]
8.3 v-for使用key
-
使用 key 可以提高效率,加 key 如果要插入 f 使用 diff 算法高效,如果使用index 做 key 一直变,所以 item 如果唯一可以使用 item。
-
将 li 与 item 绑定
-
不加 key 如果要插入 f 依次替换。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>v-for使用key</title>
</head>
<body>
<div id="app">
<!-- 不加key如果要插入f依次改变 -->
<ul>
<li v-for="item in letters">{{item}}</li>
</ul>
<button @click="add1">没有key</button>
<!-- 加key如果要插入f使用diff算法高效,如果使用index做key一直变,所以item如果唯一可以使用item-->
<ul>
<li v-for="item in letters" :key="item">{{item}}</li>
</ul>
<button @click="add2">有key</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
letters:['a','b','c','d','e']
},
methods: {
add1(){
this.letters.splice(2,0,'f')
},
add2(){
this.letters.splice(2,0,'f')
}
}
})
</script>
</body>
</html>
v-for加key与不加
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ED1wekf3-1612961537063)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\8.3-1.png)]
不加key渲染时候会依次替换渲染,加了key会直接将其放在指定位置,加key提升效率。
8.4 数组的响应方式
响应式:通过函数方法把数组内容改变后,页面内容也会刷新。
我们改变DOM绑定的数据时,DOM会动态的改变值。数组也是一样的。但是对于动态变化数据,有要求,不是任何情况改变数据都会变化。
push()
- this.letters.push(‘aaa’)
- this.letters.push(‘aaaa’, ‘bbbb’, ‘cccc’)
pop()
- 删除数组中的最后一个元素
- this.letters.pop();
shift()
- 删除数组中的第一个元素
- this.letters.shift();
unshift()
- 在数组最前面添加元素
- this.letters.unshift()
- this.letters.unshift(‘aaa’, ‘bbb’, ‘ccc’)
splice()
- 删除 / 插入 / 替换
- 删除元素
- 第二个参数传入你要删除几个元素(如果没有传,就删除后面所有的元素)
- 替换元素
- 第二个参数, 表示我们要替换几个元素, 后面是用于替换前面的元素
- this.letters.splice(1, 3, ‘m’, ‘n’, ‘l’, ‘x’)
- 插入元素
- 第二个参数, 传入0, 并且后面跟上要插入的元素
- this.letters.splice(1, 0, ‘x’, ‘y’, ‘z’)
sort()
- this.letters.sort()
reverse()
-
反转数组元素
-
this.letters.reverse()
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>数组的响应式方法 </title>
</head>
<body>
<div id="app">
<!-- 数组的响应式方法 -->
<ul>
<li v-for="item in letters">{{item}}</li>
</ul>
<button @click="btn1">push</button><br>
<button @click="btn2">通过索引值修改数组</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
letters:['a','b','c','d','e']
},
methods: {
btn1(){
//1.push
this.letters.push('f')
//2.pop()删除最后一个元素
//this.letters.pop()
//3.shift()删除第一个
//this.letters.shift()
//4.unshift()添加在最前面,可以添加多个
//this.letters.unshift('aaa','bbb','ccc')
//5.splice():删除元素/插入元素/替换元素
//splice(1,1)再索引为1的地方删除一个元素,第二个元素不传,直接删除后面所有元素
//splice(index,0,'aaa')在索引index后面删除0个元素,加上'aaa',
//splice(1,1,'aaa')替换索引为1的后一个元素为'aaa'
// this.letters.splice(2,0,'aaa')
//6.sort()排序可以传入一个函数
//this.letters.sort()
//7.reverse()反转
// this.letters.reverse()
},
btn2(){
this.letters[0]='f'
}
}
})
</script>
</body>
</html>
-
btn2按钮是通过索引值修改数组的值,这种情况,数组letters变化,DOM不会变化。
-
而数组的方法,例如
push()
、pop()
、shift()
、unshift()
、splice()
、sort()
、reverse()
等方法修改数组的数据,DOM元素会随之修改。 -
splic():删除元素、插入元素、替换元素
splice(1,1)再索引为1的地方删除一个元素,第二个元素不传,直接删除后面所有元素
splice(index,0,‘aaa’)再索引index后面删除0个元素,加上’aaa’
splice(1,1,‘aaa’)替换索引为1的后一个元素为’aaa’
-
注意: 通过索引值修改数组中的元素
this.letters[0] = ‘bbbbbb’;
this.letters.splice(0, 1, ‘bbbbbb’)
set(要修改的对象, 索引值, 修改后的值)
Vue.set(this.letters, 0, ‘bbbbbb’)
8.5 综合练习
现在要求将数组内的电影展示到页面上,并选中某个电影,电影背景变红,为选中状态。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>综合练习</title>
<style>
.active {
background-color: red;
}
</style>
</head>
<body>
<div id="app">
<!-- 数组的响应式方法 -->
<ul>
<li v-for="(item,index) in movies" @click="liClick(index)" :class="{active:index===curIndex}">{{index+"---"+item}}</li>
</ul>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
movies: ['复仇者联盟', '蝙蝠侠', '海贼王', '星际穿越'],
curIndex:0
},
methods: {
liClick(index){
this.curIndex = index
}
}
})
</script>
</body>
</html>
- 先使用
v-for
将电影列表展示到页面上,并获取index索引定位当前的<li>
标签。 - 给每个
<li>
标签加上,单击事件,并将index传入单击事件的回调函数methods的liClick()
。 - 定义一个变量
curIndex
表示当前索引,初始值为0,用于表示选中状态的电影列。 - 定义个class样式active,在active为激活状态是,
background-color: red;
为红色。使用表达式index=curIndex
判断当前选中状态的列。 - [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8lxdf8VG-1612961537064)(./images/8.5-1.gif)]
(九)综合练习
9.1 综合练习
综合前面的知识,需要通过一个小demo来串联起知识。
如图所示,
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-afAaMJ60-1612961537065)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\9-1.png)]
点击“+”按钮,总价增加,点击“-”按钮总价减少,点击移除,移除当列。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>图书购物车小案例</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="app">
<table>
<thead>
<th> </th>
<th>书籍名称</th>
<th>出版日期</th>
<th>价格</th>
<th>购买数量</th>
<th>操作</th>
</thead>
<tbody>
<tr v-for="(book, index) in books" :key="index">
<td>{{index}}</td>
<td>{{book.name}}</td>
<td>{{book.beginDate}}</td>
<td>{{book.price | showPrice}}</td>
<td>
<button @click="decrement(index)" :disabled="book.count<=1" >-</button>
{{book.count}}
<button @click="increment(index)">+</button>
</td>
<td><button @click="remove">移除</button></td>
</tr>
</tbody>
</table>
<h3>总价:{{totalPrice | showPrice}}</h3>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script src="main.js"></script>
</body>
</html>
使用计算属性记录总价,使用v-for
循环遍历数组变量,使用table输出到html页面上。
js代码
const app = new Vue({
el: "#app",
data: {
books: [{
name: "《算法导论》",
beginDate: "2006-9",
price: 85.00,
count: 1
},
{
name: "《UNIX编程艺术》",
beginDate: "2006-2",
price: 59.00,
count: 1
},
{
name: "《编程大全》",
beginDate: "2008-10",
price: 39.00,
count: 1
},
{
name: "《代码大全》",
beginDate: "2006-3",
price: 128.00,
count: 1
},
]
},
computed: {
totalPrice () {
let total = 0;
//1.普通for循环
// for (let i = 0; i < this.books.length; i++) {
// total = total + this.books[i].price * this.books[i].count
// }
// 2.增强for循环
// for (let i in this.books) {
// total = total + this.books[i].price * this.books[i].count
// }
// 3.for of
// for (const book of this.books) {
// total = total + book.price * book.count
// }
// return total
// 4.使用高阶函数
// return this.books.map(function (book) {
// return book.price * book.count
// }).reduce(function (preValue,currentValue) {
// return preValue + currentValue
// })
// 5.高阶函数简写(箭头函数)
return this.books.length === 0 ? 0 : this.books.map(book => book.price * book.count).reduce((preValue,currentVlue) => preValue + currentVlue)
}
},
methods: {
increment(index){
this.books[index].count++
},
decrement(index){
this.books[index].count--
},
remove(index){
this.books.splice(index,1)
}
},
filters:{//过滤器
showPrice(price){
return "¥" + price.toFixed(2)
}
}
})
// 1.filter过滤函数
const nums = [2,3,5,1,77,55,100,200]
//要求获取nums中大于50的数
//回调函数会遍历nums中每一个数,传入回调函数,在回调函数中写判断逻辑,返回true则会被数组接收,false会被拒绝
let newNums = nums.filter(function (num) {
if(num > 50){
return true;
}
return false;
})
//可以使用箭头函数简写
// let newNums = nums.filter(num => num >50)
console.log(newNums);
// 2.map高阶函数
// 要求将已经过滤的新数组每项乘以2
//map函数同样会遍历数组每一项,传入回调函数为参数,num是map遍历的每一项,回调函数function返回值会被添加到新数组中
let newNums2 = newNums.map(function (num) {
return num * 2
})
//简写
// let newNums2 = newNums.map(num => num * 2)
console.log(newNums2);
// 3.reduce高阶函数
//要求将newNums2的数组所有数累加
//reduce函数同样会遍历数组每一项,传入回调函数和‘0’为参数,0表示回调函数中preValue初始值为0,回调函数中参数preValue是每一次回调函数function返回的值,currentValue是当前值
//例如数组为[154, 110, 200, 400],则回调函数第一次返回值为0+154=154,第二次preValue为154,返回值为154+110=264,以此类推直到遍历完成
let newNum = newNums2.reduce(function (preValue,currentValue) {
return preValue + currentValue
},0)
//简写
// let newNum = newNums2.reduce((preValue,currentValue) => preValue + currentValue)
console.log(newNum);
//三个需求综合
let n = nums.filter(num => num > 50).map(num => num * 2).reduce((preValue,currentValue) => preValue + currentValue)
console.log(n);
使用books数组对象记录book数据,使用totalPrice计算属性计算总价。
- 使用普通for循环
- 使用增强for循环数组索引
- 使用for of,直接循环数组内的对象
- 使用高阶函数map对象计算每个book对象的总价,在使用reduce累加总价。
css
table{
border: 1px;
border-collapse: collapse;
border-spacing: 0;
}
th,td{
padding: 8px 16px;
border: ipx solid #e9e9e9;
text-align: left;
}
th{
background-color: #f7f7f7;
color: #5c6b77;
font-weight: 600;
}
(十)v-model
10.1 v-model的基本使用
<div id="app">
<!-- 输入框内容修改,message也修改,修改message,input内容也修改,双向绑定 -->
<input type="text" v-model="message">{{message}}
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
message:"zzz"
}
})
</script>
v-model双向绑定,既输入框的value改变,对应的message对象值也会改变,修改message的值,input的value也会随之改变。无论改变那个值,另外一个值都会变化。
10.2 v-model的原理
先来一个demo实现不使用v-model实现双向绑定。
<div id="app">
<!-- v-model = v-bind + v-on -->
<!-- 输入框内容修改,message也修改,修改message,input内容也修改,双向绑定 -->
<!-- <input type="text" v-model="message"> -->
<!-- 实现双向绑定 @input监听输入框事件 -->
<!-- <input type="text" :value="message" @input="valueChange" > -->
<!-- $event获取事件对象,$event.target.value获取input值 -->
<input type="text" :value="message" @input="valueChange($event.target.value)">
{{message}}
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
message:"zzz"
},
methods:{
// valueChange(event){
// console.log("input值改变了");
// this.message = event.target.value
// },
valueChange(value){
console.log("input值改变了");
this.message = value
}
}
})
</script>
v-model = v-bind + v-on
实现双向绑定需要是用v-bind和v-on,使用v-bind给input的value绑定message对象,此时message对象改变,input的值也会改变。但是改变input的value并不会改变message的值,此时需要一个v-on绑定一个方法,监听事件,当input的值改变的时候,将最新的值赋值给message对象。$event
获取事件对象,target获取监听的对象dom,value获取最新的值。
10.3 v-model结合radio类型使用
radio单选框的name
属性是互斥的,如果使用v-model,可以不使用name
就可以互斥。
<div id="app">
<!-- name属性radio互斥 使用v-model可以不用name就可以互斥 -->
<label for="male">
<input type="radio" id="male" name="sex" value="男" v-model="sex">男
</label>
<label for="female">
<input type="radio" id="female" name="sex" value="女" v-model="sex">女
</label>
<div>你选择的性别是:{{sex}}</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
message:"zzz",
sex:"男"
},
})
</script>
v-model绑定`sex`属性,初始值为“男”,选择女后`sex`属性变成“女”,因为此时是双向绑定。
10.4 v-model结合checkbox类型
checkbox可以结合v-model做单选框,也可以多选框。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>v-model结合checkbox类型</title>
</head>
<body>
<div id="app">
<!-- checkbox单选框 -->
<h2>单选框</h2>
<label for="agree">
<input type="checkbox" id="agree" v-model="isAgree">同意协议
</label>
<div>你选择的结果是:{{isAgree}}</div>
<button :disabled="!isAgree">下一步</button>
<!-- checkbox多选框 -->
<h2>多选框</h2>
<label :for="item" v-for="(item, index) in oriHobbies" :key="index">
<input type="checkbox" name="hobby" :value="item" :id="item" v-model="hobbies">{{item}}
</label>
<!-- <input type="checkbox" name="hobby" value="篮球" v-model="hobbies">篮球
<input type="checkbox" name="hobby" value="足球" v-model="hobbies">足球
<input type="checkbox" name="hobby" value="羽毛球" v-model="hobbies">羽毛球
<input type="checkbox" name="hobby" value="乒乓球" v-model="hobbies">乒乓球 -->
<div>你的爱好是:{{hobbies}}</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
message:"zzz",
isAgree:false,
hobbies:[],
oriHobbies:["篮球","足球","羽毛球","乒乓球"]
},
})
</script>
</body>
</html>
- checkbox结合v-model实现单选框,定义变量
isAgree
初始化为false
,点击checkbox的值为true
,isAgree
也是true
。 - checkbox结合v-model实现多选框,定义数组对象
hobbies
,用于存放爱好,将hobbies
与checkbox对象双向绑定,此时选中,一个多选框,就多一个true,hobbies
就添加一个对象。
10.5 v-model结合select类型
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>v-model结合select类型</title>
</head>
<body>
<div id="app">
<!-- select单选 -->
<select name="fruit" v-model="fruit">
<option value="苹果">苹果</option>
<option value="香蕉">香蕉</option>
<option value="西瓜">西瓜</option>
</select>
<h2>你选择的水果是:{{fruit}}</h2>
<!-- select多选 -->
<select name="fruits" v-model="fruits" multiple>
<option value="苹果">苹果</option>
<option value="香蕉">香蕉</option>
<option value="西瓜">西瓜</option>
</select>
<h2>你选择的水果是:{{fruits}}</h2>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
fruit:"苹果",
fruits:[]
},
})
</script>
</body>
v-model结合select可以单选也可以多选。
10.6 v-model的修饰符的使用
10.6.1 lazy
lazy
默认情况下是实时更新数据,加上lazy
,从输入框失去焦点,按下enter都会更新数据。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>v-model修饰符</title>
</head>
<body>
<div id="app">
<h2>v-model修饰符</h2>
<h3>lazy,默认情况是实时更新数据,加上lazy,从输入框失去焦点,按下enter都会更新数据</h3>
<input type="text" v-model.lazy="message">
<div>{{message}}</div>
<h3>修饰符number,默认是string类型,使用number赋值为number类型</h3>
<input type="number" v-model.number="age">
<div>{{age}}--{{typeof age}}</div>
<h3>修饰符trim:去空格</h3>
<input type="text" v-model.trim="name">
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
message:"zzz",
age:18,
name:"ttt"
},
})
</script>
</body>
</html>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ImEKzp4c-1612961537066)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\10.6-1.png)]
10.6.2 number
-
v-model 默认绑定成字符串。
-
使用
number
复制为number类型。
10.6.3 trim
trim
用于 自动过滤用户输入的首尾空白字符
(十一)组件化开发
11.1 组件化的基本使用
- 三步骤
- 创建组件构造器
- Vue.extend()
- 必须有root元素
- Vue.extend()
- 注册组件
- Vue.component()
- 使用组件
- 在Vue实例作用范围内使用组件
- 创建组件构造器
注意
- Vue.extend()和Vue.component()必须在new Vue之前,且新标签必须使用在有定义的new Vue div中。
- 注册组件名不能使用驼峰命名法。
- script标签里,先创建组件,注册组件,后 new Vue 对象监听id
- 简单的组件示例
<div id="app">
<!-- 3.使用组件 -->
<my-cpn></my-cpn>
<my-cpn></my-cpn>
<my-cpn></my-cpn>
<cpnc></cpnc>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
// 1.创建组件构造器对象
const cpnc = Vue.extend({
template:`
<div>
<h2>标题</h2>
<p>内容1...<p>
<p>内容2...<p>
</div>`
})
// 2.注册组件
Vue.component('my-cpn', cpnc)
const app = new Vue({
el:"#app",
data:{
},
components:{//局部组件创建
cpnc:cpnc
}
})
</script>
组件是可复用的 Vue 实例,且带有一个名字:在这个例子中是 my-cpn
。我们可以在一个通过 new Vue
创建的 Vue 根实例中,把这个组件作为自定义元素来使用: <my-cpn></my-cpn>
。
创建组件构造器对象
template
中是组件的DOM元素内容。
注册组件
- 全局注册,通过
Vue.component
。 - 局部注册,通过
components:{cpnc:cpnc}
。
使用组件
像使用html标签一样使用。
<div id="app">
<!-- 3.使用组件 -->
<my-cpn></my-cpn>
<my-cpn></my-cpn>
<my-cpn></my-cpn>
<cpnc></cpnc>
</div>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tzUM5Y1U-1612961537067)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\11.1-1.png)]
11.2 全局组件和局部组件
组件的注册方式有两种,一种是全局组件一种是局部组件。
<div id="app">
<h2>全局组件</h2>
<my-cpn></my-cpn>
<h2>局部组件</h2>
<cpnc></cpnc>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
// 1.创建组件构造器对象
const cpnc = Vue.extend({
template:`
<div>
<h2>标题</h2>
<p>内容1</p>
<p>内容2</p>
</div>`
})
// 2.注册组件(全局组件,可以在多个vue实例中使用)
Vue.component('my-cpn', cpnc)
const app = new Vue({
el:"#app",
components:{//局部组件创建
cpnc:cpnc
}
})
</script>
全局组件
全局组件,可以在多个vue实例中使用,类似于全局变量。
使用Vue.component('my-cpn', cpnc)
方式注册,直接使用<my-cpn></my-cpn>
调用。my-cpn
是全局组件的名字,cpnc
是定义的组件对象。
局部组件
局部组件,只能在当前vue实例挂载的对象中使用,类似于局部变量,有块级作用域。
注册方式
const app = new Vue({
el:"#app",
components:{//局部组件创建
cpnc:cpnc
}
})
使用方式与全局变量一样,直接使用<cpnc></cpnc>
调用。cpnc:cpnc
第一个cpnc是给组件命名的名字,第二个是定义的组件对象。如果俩个同名也可以直接使用es6语法:
components:{//局部组件创建
cpnc
}
11.3 区别父组件与子组件
组件1(子组件) → 组件2(父组件) → vue实例
在哪里注册哪里就是父组件,
vue实例也可以看成最顶层的组件 (root)
“附庸的附庸不是我的附庸”
<div id="app">
<cpn2></cpn2>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
// 1.创建组件构造器对象
const cpn1 = Vue.extend({
template:`
<div>
<h2>标题1</h2>
<p>组件1</p>
</div>`
})
// 组件2中使用组件1
const cpn2 = Vue.extend({
template:`
<div>
<h2>标题2</h2>
<p>组件2</p>
<cpn1></cpn1>
</div>`,
components:{
cpn1:cpn1
}
})
const app = new Vue({
el:"#app",
components:{//局部组件创建
cpn2:cpn2
}
})
</script>
上述代码中定义了两个组件对象cpn1
和cpn2
,在组件cpn2
中使用局部组件注册了cpn1
,并在template
中使用了注册的cpn1
,然后在vue实例中使用注册了局部组件cpn2
,在vue实例挂载的div中调用了cpn2
,cpn2
与cpn1
形成父子组件关系。
注意:组件就是一个vue实例,vue实例的属性,组件也可以有,例如data、methods、computed等。
11.4 注册组件语法糖
-
直接在注册的时候实例化
-
{}
就是一个组件对象。 -
const cpnC1 = Vue.extend({ template: ``})
Vue.component(‘cpn1’,Cpn)
Vue.component(‘cpn1’, {template: ``}) //全局注册
components : { ‘cpn2’: { template: ``} //局部注册
<div id="app">
<cpn1></cpn1>
<cpn2></cpn2>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
// 1.注册全局组件语法糖
Vue.component('cpn1', {
template:`
<div>
<h2>全局组件语法糖</h2>
<p>全局组件语法糖</p>
</div>`
})
const app = new Vue({
el:"#app",
components:{//局部组件创建
cpn2:{
template:`
<div>
<h2>局部组件语法糖</h2>
<p>局部组件语法糖</p>
</div>`
}
}
})
</script>
注册组件时候可以不实例化组件对象,直接在注册的时候实例化。{}
就是一个组件对象。
11.5 组件模板的分离
使 html 代码与 js 代码分离
script标签
- 使用
script
标签定义组件的模板,script
标签注意类型是text/x-template
。
<!-- 1.script标签注意类型是text/x-template -->
<script type="text/x-template" id="cpn1">
<div>
<h2>组件模板的分离写法</h2>
<p>script标签注意类型是text/x-template</p>
</div>
</script>
template标签
- 使用
template
标签,将内容写在标签内。
<!-- 2.template标签 -->
<template id="cpn2">
<div>
<h2>组件模板的分离写法</h2>
<p>template标签</p>
</div>
</template>
调用
- 调用分离的模板,使用
template:'#cpn1'
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
components: { //局部组件创建
cpn1:{
template:'#cpn1'
},
cpn2: {
template: '#cpn2'
}
}
})
</script>
11.6 组件的数据
存放问题
- 前面说过vue组件就是一个vue实例,相应的vue组件也有
data
属性来存放数据。
<div id="app">
<cpn1></cpn1>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
components: { //局部组件创建
cpn1:{
template:'<div>{{msg}}</div>',
data(){
return {
msg:"组件的数据存放必须要是一个函数"
}
}
}
}
})
</script>
在template
中使用组件内部的数据msg
。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zpb0qiAf-1612961537068)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\11.6-1.png)]
组件的data为什么必须要是函数
- 这是js语言本身特性带来的问题
- 组件的思想是复用,定义组件当然是把通用的公共的东西抽出来复用。
<div id="app">
<h2>data不使用函数</h2>
<cpn1></cpn1>
<cpn1></cpn1>
<hr>
<h2>data使用函数</h2>
<cpn2></cpn2>
<cpn2></cpn2>
<hr>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<template id="cpn1">
<div>
<button @click="count--">-</button>
当前计数:{{count}}
<button @click="count++">+</button>
</div>
</template>
<template id="cpn2">
<div>
<button @click="count--">-</button>
当前计数:{{count}}
<button @click="count++">+</button>
</div>
</template>
<script>
const obj = {
count:0
};
const app = new Vue({
el: "#app",
components: { //局部组件创建
cpn1: {
template: '#cpn1',
data() {
// 每次new的实例指向同一个对象:obj,所以实例的count都是同一个变量,相互关联
return obj;
}
},
cpn2: {
template: '#cpn2',
data() {
return {
// 每次new的实例都是一个新的内存地址,所以count变量也是不同变量
count: 0
}
}
}
}
})
</script>
上述代码中定义了两个组件cpn1
和cpn2
,都是定义了两个计数器,con1
的data虽然使用了函数,但是为了模拟data:{count:0}
,使用了常量obj
来返回count。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZOk8Lego-1612961537069)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\11.6-2.gif)]
图中可以看到,不使用data
的好像共用一个count
属性,而使用函数的data
的count是各自用各自的,像局部变量一样有块级作用域,这个块级就是vue组件的作用域。
我们在复用组件的时候肯定希望,各自组件用各自的变量,如果确实需要都用一样的,可以全局组件注册,也可以是用vuex来进行状态管理。
补充
引用数据类型
Object是引用数据类型,如果不用function返回,每个组件的data都是内存的同一个地址,一个数据改变了其他也改变了;
JavaScript只有函数构成作用域 (注意理解作用域,只有函数{}构成作用域,对象的 {} 以及 if(){} 都不构成作用域), data是一个函数时,每个组件实例都有自己的作用域,每个实例相互独立,不会相互影响。
如果两个实例同事引用一个对象,那么当你修改其中一个属性的时候,另外一个实例也会跟着该;
11.7 组件的通信
方法:通过proos向子组件传递消息、通过事件向父组件发送消息($emit events)
- 思想:顶层数据传向下层
父传子
props
属性:父传子
1.使用子组件的
props
属性
const cpn = {
template: "#cpn",
props: {
cmessage: {
type: String,
default: 'zzzzz',
required: true //在使用组件必传值
}
}
}
2.向cmessage对象传值
<div id="app">
<cpn :cMessage="message"></cpn>
</div>
<script>
const app = new Vue({
el: "#app",
data: {
message: "你好",
movies: ["复仇者联盟", "钢铁侠", "星际穿越", "哪吒传奇"]
},
components: {
cpn
}
})
</script>
props属性使用
数组写法
props: ['cmovies', 'cmessage']
对象写法
props: {
cmessage: {
type: String,
default: 'zzzzz',
required: true //在使用组件必传值
}
}
props属性的类型限制
//1.类型限制(多个类使用数组)
cmovies:Array,//限制为数组类型
cmessage:String,//限制为Strin类型
cmessage:['String','Number']//限制为String或Number类型
props属性的默认值
// 2.提供一些默认值,以及必传值
cmessage: {
type: String,
default: 'zzzzz',//默认值
}
props属性的必传值
cmessage: {
type: String,
default: 'zzzzz',
required: true //在使用组件必传值
}
类型是Object/Array,默认值必须是一个函数
//类型是Object/Array,默认值必须是一个函数
cmovies: {
type: Array,
default () {
return [1, 2, 3, 4]
}
},
自定义验证函数
vaildator: function (value) {
//这个传递的值必须匹配下列字符串中的一个
return ['zzzzz', 'ttttt', 'yyy'].indexOf(value) !== -1
}
自定义类型
function Person(firstName,lastName) {
this.firstName = firstName
this.lastName = lastName
}
cmessage:Person//限定了cmeessage必须是Person类型
//类型的标识符都以大写字母开头
综合使用
<div id="app">
<cpn :cMovies="movies" :cMessage="message"></cpn>
</div>
<template id="cpn">
<div>
<ul>
<li v-for="(item, index) in cmovies" :key="index">{{item}}</li>
</ul>
<h2>{{cmessage}}</h2>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
function Person(firstName,lastName) {
this.firstName = firstName
this.lastName = lastName
}
// 父传子:props
const cpn = {
template: "#cpn",
// props: ['cmovies', 'cmessage'],//数组写法
props: { //对象写法
// 1.类型限制(多个类使用数组)
// cmovies:Array,
// cmessage:String,
// cmessage:['String','Number'],
// 2.提供一些默认值,以及必传值
cmessage: {
type: String,
default: 'zzzzz',
required: true //在使用组件必传值
},
//类型是Object/Array,默认值必须是一个函数
cmovies: {
type: Array,
default () {
return [1, 2, 3, 4]
}
},
// 3.自定义验证函数
// vaildator: function (value) {
// //这个传递的值必须匹配下列字符串中的一个
// return ['zzzzz', 'ttttt', 'yyy'].indexOf(value) !== -1
// }
// 4.自定义类型
// cmessage:Person,
},
data() {
return {
}
},
methods: {
},
};
const app = new Vue({
el: "#app",
data: {
message: "你好",
movies: ["复仇者联盟", "钢铁侠", "星际穿越", "哪吒传奇"]
},
components: {
cpn
}
})
</script>
props的驼峰标识
- v-bind是 不支持使用驼峰标识的
cUser
即c-User
(绑定时)。
<div id="app">
<!-- v-bind不支持驼峰 :cUser改成 :c-User-->
<!-- <cpn :cUser="user"></cpn> -->
<cpn :c-User="user"></cpn>
<cpn :cuser="user" ></cpn>
</div>
<template id="cpn">
<div>
<!-- 使用驼峰 -->
<h2>{{cUser}}</h2>
<!-- 不使用 -->
<h2>{{cuser}}</h2>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
// 父传子:props
const cpn = {
template: "#cpn",
props: { //对象写法
//驼峰
cUser:Object,
//未使用驼峰
cuser:Object
},
data() {return {}},
methods: {},
};
const app = new Vue({
el: "#app",
data: {
user:{
name:'zzz',
age:18,
height:175
}
},
components: {
cpn
}
})
</script>
子传父$emit
-
子组件向父组件传值,使用自定义事件
$emit
。 -
核心
-
子组件$emit() 触发事件
-
父组件:v-on监听
-
案例
<!-- 父组件 -->
<div id="app">
<!-- 不写参数默认传递btnClick的item -->
<cpn @itemclick="cpnClcik"></cpn>
</div>
<!-- 子组件 -->
<template id="cpn">
<div>
<button v-for="(item, index) in categoties" :key="index" @click="btnClick(item)">{{item.name}}</button>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
// 父传子:props
const cpn = {
template: "#cpn",
data() {
return {
categoties: [{
id: 'aaa',
name: '热门推荐'
},
{
id: 'bbb',
name: '手机数码'
},
{
id: 'ccc',
name: '家用家电'
},
{
id: 'ddd',
name: '电脑办公'
},
]
}
},
methods: {
btnClick(item) {
this.$emit('itemclick', item)
}
},
};
const app = new Vue({
el: "#app",
data() {
return {
}
},
methods: {
cpnClcik(item) {
console.log('cpnClick'+item.name);
}
},
components: {
cpn
},
})
</script>
步骤
1.在子组件中定义一个方法btnClick(item)
,使用$emit
,'itemclick’是事件名,item
是传过去的值。
methods: {
btnClick(item) {
this.$emit('itemclick', item)
}
},
2.在子组件中监听点击事件并回调此方法
<div>
<button v-for="(item, index) in categoties" :key="index" @click="btnClick(item)">{{item.name}}</button>
</div>
3.在父组件中定义一个方法cpnClcik(item)
methods: {
cpnClcik(item) {
console.log('cpnClick'+item.name);
}
},
4.并在父组件(vue实例)中调用<cpn @itemclick="cpnClcik"></cpn>
(不写参数默认传递btnClick的item ),父组件监听事件名为itemclick
的子组件传过来的事件。
<cpn @itemclick="cpnClcik"></cpn>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6T8Rr7x3-1612961537071)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\11.8-1.gif)]
父子组件通信案例
-
实现父子组件的值双向绑定。
-
问题
- 不推荐子组件直接修改父组件数据
方案一
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>组件通信-父子通信案例</title>
</head>
<body>
<!-- 父组件 -->
<div id="app">
<cpn :number1='num1' :number2='num2' @num1change="num1Change" @num2change="num2Change"></cpn>
<h2>父组件{{num1}}</h2>
<input type="text" v-model="num1" >
<h2>父组件{{num2}}</h2>
<input type="text" v-model="num2">
</div>
<!-- 子组件 -->
<template id="cpn">
<div>
<h2>{{number1}}</h2>
<h2>{{dnumber1}}</h2>
<input type="text" :value="dnumber1" @input="num1input">
<h2>{{number2}}</h2>
<input type="text" :value="dnumber2" @input="num2input">
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
// 父传子:props
const cpn = {
template: "#cpn",
data() {
return {
dnumber1:this.number1,
dnumber2:this.number2
}
},
props:{
number1:[Number,String],
number2:[Number,String],
},
methods: {
num1input(event){
this.dnumber1 = event.target.value
this.$emit('num1change',this.dnumber1)
},
num2input(event){
this.dnumber2 = event.target.value
this.$emit('num2change',this.dnumber2)
}
},
};
const app = new Vue({
el: "#app",
data() {
return {
num1:1,
num2:2,
}
},
methods: {
num1Change(value){
this.num1=value
},
num2Change(value){
this.num1=value
}
},
components: {
cpn
},
})
</script>
</body>
</html>
方案二 使用watch实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>组件通信-父子通信案例(watch实现)</title>
</head>
<body>
<!-- 父组件 -->
<div id="app">
<cpn :number1='num1' :number2='num2' @num1change="num1Change" @num2change="num2Change"></cpn>
<h2>父组件{{num1}}</h2>
<input type="text" v-model="num1" >
<h2>父组件{{num2}}</h2>
<input type="text" v-model="num2">
</div>
<!-- 子组件 -->
<template id="cpn">
<div>
<h2>{{number1}}</h2>
<input type="text" v-model="dnumber1">
<h2>{{number2}}</h2>
<input type="text" v-model="dnumber2">
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
// 父传子:props
const cpn = {
template: "#cpn",
data() {
return {
dnumber1:this.number1,
dnumber2:this.number2
}
},
props:{
number1:[Number,String],
number2:[Number,String],
},
watch: {
dnumber1(newValue){
this.dnumber1 = newValue * 100
this.$emit('num1change',newValue)
},
dnumber2(newValue){
this.dnumber1 = newValue * 100
this.$emit('num2change',newValue)
}
},
};
const app = new Vue({
el: "#app",
data() {
return {
num1:1,
num2:2,
}
},
methods: {
num1Change(value){
this.num1=value
},
num2Change(value){
this.num1=value
}
},
components: {
cpn
},
})
</script>
</body>
</html>
11.8 父子组件的访问方式
父访问子 children-ref
- 父组件访问子组件,有时候我么你需要直接操作子组件的方法,或是属性,此时需要用到
$children
和$ref
。
$children方式
- 不常用,一般获取所有子组件时使用。
// 1.children
console.log(this.$children[0].showMessage)
for (let cpn of this.$children) {
console.log(cpn.showMessage)
}
使用this.$children
直接获取**当前实例的直接子组件,需要注意 $children
并不保证顺序,也不是响应式的。**如果你发现自己正在尝试使用 $children
来进行数据绑定,考虑使用一个数组配合 v-for
来生成子组件,并且使用 Array 作为真正的来源。
$refs方式
- reference(引用)
- $refs对象类型, 默认是一个空的对象
1.先定义子组件
<cpn ref="aaa"></cpn>
2.直接调用
案例
<!-- 父组件 -->
<div id="app">
<cpn></cpn>
<cpn></cpn>
<cpn ref="aaa"></cpn>
<button @click="btnClick" >按钮</button>
</div>
<!-- 子组件 -->
<template id="cpn">
<div>
我是子组件
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
// 父传子:props
const cpn = {
template: "#cpn",
data() {
return {
name:"我是子组件的name"
}
},
methods: {
showMessage(){
console.log("showMessage");
}
},
};
const app = new Vue({
el: "#app",
data() {
return {
message:"hello"
}
},
methods: {
btnClick(){
// 1.children
// console.log(this.$children[0].showMessage)
// for (let cpn of this.$children) {
// console.log(cpn.showMessage)
// }
// 2.$ref
console.log(this.$refs.aaa.name)
}
},
components: {
cpn
},
})
</script>
子访问父 parent-root
$parent方式
methods: {
btnClick() {
// 1.访问父组件$parent
console.log(this.$parent);
console.log(this.$parent.name);
}
$root方式
methods: {
btnClick() {
console.log(this.$root);
console.log(this.$root.name);
}