后续会学习setup,这些api都不会再使用了,但是我们必须学会这些基础,才能更好的学习setup
v-on指令绑定事件
以前我们就写过@click=“”,其实@是v-on:的语法糖
基本使用
v-on不仅可以绑定click事件,原先学的dom事件都可以绑定,只是click事件比较常用。
<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>
<style>
.box {
width: 100px;
height: 100px;
background-color: orange;
margin-top: 10px;
}
</style>
</head>
<body>
<div id="app">
<!-- 1.基本的写法 -->
<div class="box" v-on:click="divClick"></div>
<!-- 2.语法糖写法(重点掌握) -->
<div class="box" @click="divClick"></div>
<!-- 3.绑定的方法位置, 也可以写成一个表达式(不常用, 不推荐) -->
<h2>{{ counter }}</h2>
<button @click="increment">+1</button>
<!-- 这里counter++就直接是点击这个按钮会发生的程序,而不是有一个counter++名字的函数>>
<button @click="counter++">+1</button>
<!-- 4.绑定其他方法(掌握) -->
<div class="box" @mousemove="divMousemove"></div>
<!-- 5.元素绑定多个事件(掌握) -->
<div class="box" @click="divClick" @mousemove="divMousemove"></div>
<!-- <div class="box" v-on="{ click: divClick, mousemove: divMousemove }"></div> -->
<!-- <div class="box" @="{ click: divClick, mousemove: divMousemove }"></div> -->
</div>
<script src="../lib/vue.js"></script>
<script>
// 1.创建app
const app = Vue.createApp({
// data: option api
data: function() {
return {
counter: 0
}
},
methods: {
divClick() {
console.log("divClick")
},
increment() {
this.counter++
},
divMousemove() {
console.log("divMousemove")
}
}
})
// 2.挂载app
app.mount("#app")
</script>
</body>
</html>
<button @click="increment">+1</button>
<!-- 这里counter++就直接是点击这个按钮会发生的程序,而不是有一个counter++名字的函数>>
<button @click="counter++">+1</button>
如上,click里面不一定要绑定一个methods里的函数,也可以直接写表达式进去。(不建议,阅读性很差)
<!-- <div class="box" v-on="{ click: divClick, mousemove: divMousemove }"></div> -->
<!-- <div class="box" @="{ click: divClick, mousemove: divMousemove }"></div> -->
<div class="box" @click="divClick" @mousemove="divMousemove"></div>
v-on可以同时绑定多个事件,如上代码,通常用第三种。
v-on的传递参数
<body>
<div id="app">
<!-- 1.默认传递event对象 -->
<button @click="btn1Click">按钮1</button>
<!-- 2.只有自己的参数 -->
<button @click="btn2Click('why', age)">按钮2</button>
<!-- 3.自己的参数和event对象 -->
<!-- 在模板中想要明确的获取event对象: $event -->
<button @click="btn3Click('why', age, $event)">按钮3</button>
</div>
<script src="../lib/vue.js"></script>
<script>
// 1.创建app
const app = Vue.createApp({
// data: option api
data: function() {
return {
message: "Hello Vue",
age: 18
}
},
methods: {
// 1.默认参数: event对象
// 总结: 如果在绑定事件的时候, 没有传递任何的参数, 那么event对象会被默认传递进来
btn1Click(event) {
console.log("btn1Click:", event)
},
// 2.明确参数:
btn2Click(name, age) {
console.log("btn2Click:", name, age)
},
// 3.明确参数+event对象
btn3Click(name, age, event) {
console.log("btn3Click:", name, age, event)
}
}
})
// 2.挂载app
app.mount("#app")
</script>
</body>
情况一:在事件绑定的时候不传入参数,也就是函数不加括号写东西进去,那么自动有一个默认参数event,里面包含了点击等各种信息。
<button @click="btn1Click">按钮1</button>
//前提是methods里面的函数写了一个形参如btn1Click(event),如果是btn1Click(),那么也是没有event能在方法中被使用。
methods: {
// 1.默认参数: event对象
// 总结: 如果在绑定事件的时候, 没有传递任何的参数, 那么event对象会被默认传递进来
btn1Click(event) {
console.log("btn1Click:", event)
}
}
情况二:
<button @click="btn2Click('why', age)">按钮2</button>
传入参数,上面的语句,虽然加了括号,但是不会直接去执行btn2Click这个函数,vue已经对它进行过特殊处理了
只有当事件触发的时候,它会把参数传递给methods里面声明的函数,然后执行。
但是我们传入了参数,默认参数event就会消失。如果我们既要传递自己的参数又要默认参数event,就是情况三
情况三:
<button @click="btn3Click('why', age, $event)">按钮3</button>
event必须前面加一个$符号,就能传入默认参数,这是vue规定的,记住就好。
绑定事件v-on的修饰符用法
修饰符就可以自动帮我们完成一些操作,比如.stop就是阻止冒泡 .prevent就是阻止默认行为
<button @click.stop="btnClick">按钮</button>
条件渲染
v-if和v-else的demo
<body>
<!-- 模板语法 -->
<div id="app">
<ul v-if="names.length > 0">
<li v-for="item in names">{{item}}</li>
</ul>
<h2 v-else>当前names没有数据, 请求获取数据后展示</h2>
</div>
<script src="../lib/vue.js"></script>
<script>
// 1.创建app
const app = Vue.createApp({
// data: option api
data: function() {
return {
names: []
}
},
})
// 2.挂载app
app.mount("#app")
</script>
</body>
如上,v-if=“”中的条件成立的时候,才渲染li,不成立的话渲染一行h2,其中v-if和if语句一样成立与否看里面语句返回的布尔值。
判断对象中的数据是否为空
上面代码用了v-if和v-else,其中数据是数组,我们可以通过数组.length来判断有没有数据,那么对象怎么判断呢
<div class="info" v-if="Object.keys(info).length">
我们可以通过获取此对象的key来判断有没有数据,它会返回一个数组
v-else-if
<body>
<div id="app">
<h1 v-if="score > 90">优秀</h1>
<h2 v-else-if="score > 80">良好</h2>
<h3 v-else-if="score >= 60">及格</h3>
<h4 v-else>不及格</h4>
</div>
<script src="../lib/vue.js"></script>
<script>
// 1.创建app
const app = Vue.createApp({
// data: option api
data() {
return {
score: 40
}
},
})
// 2.挂载app
app.mount("#app")
</script>
</body>
也就是说,如果条件不成立,它不是通过上面dispaly;none来隐藏这个对象,它在浏览器中是根本就不会出现这个标签。
template元素的使用
也就是说,我们以前写指令,比如v-if,如果我们需要让这个指令控制多个元素的显示,一般通过div包裹这些元素(vue2中都是这样做),但是这样就会在浏览器中多出一个没必要的div标签,增加了性能损耗
所以在vue3有一个template标签元素,专门用来包括多个标签写指令,但是在浏览器中不会渲染它,节省了性能
注意:你写了标签在template里面,那么必须给template写v-if等指令,否则不显示template内的内容。
而且template不能和v-show使用
小案例二维码的显示和隐藏
<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>
<style>
img {
width: 200px;
height: 200px;
}
</style>
</head>
<body>
<div id="app">
<div>
<button @click="toggle">切换</button>
</div>
<template v-if="isShowCode">
<img src="https://game.gtimg.cn/images/yxzj/web201706/images/comm/floatwindow/wzry_qrcode.jpg" alt="">
</template>
</div>
<script src="../lib/vue.js"></script>
<script>
// 1.创建app
const app = Vue.createApp({
// data: option api
data() {
return {
isShowCode: true
}
},
methods: {
toggle() {
this.isShowCode = !this.isShowCode
}
}
})
// 2.挂载app
app.mount("#app")
</script>
</body>
</html>
v-show的使用
v-show和v-if作用一样,条件成立则显示元素。
1-v-show不成立的时候,浏览器中是由dom元素的,只是用display:none隐藏了,但是v-if就是直接消耗,标签直接无了
注意这里是display:none不是display:hidden。
hidden隐藏后dom元素标签还存在,只是东西不见了,但是还占标准流。
none是不占标准流了,也就是说none后其他元素会顶替它的位置
2-所以v-show不能使用template,因为template根本不会出现在浏览器中,那么v-show就无法通过给template来设置display来达到隐藏和显示效果,所以只能用div。
3-当一个元素需要经常判断显示隐藏就用v-show,因为如果用v-if就要来回创建销毁dom,会频繁引起回流重绘,导致性能降低。
模板语法二
v-for列表渲染
<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>
<style>
.item {
margin-top: 5px;
background-color: orange;
}
.item .title {
color: red;
}
</style>
</head>
<body>
<div id="app">
<!-- 1.电影列表进行渲染 -->
<h2>电影列表</h2>
<ul>
<li v-for="movie in movies">{{ movie }}</li>
</ul>
<!-- 2.电影列表同时有索引 -->
<ul>
<li v-for="(movie, index) in movies">{{index + 1}} - {{ movie }}</li>
</ul>
<!-- 3.遍历数组复杂数据 -->
<h2>商品列表</h2>
<div class="item" v-for="item in products">
<h3 class="title">商品: {{item.name}}</h3>
<span>价格: {{item.price}}</span>
<p>秒杀: {{item.desc}}</p>
</div>
</div>
<script src="../lib/vue.js"></script>
<script>
// 1.创建app
const app = Vue.createApp({
// data: option api
data() {
return {
// 1.movies
movies: ["星际穿越", "少年派", "大话西游", "哆啦A梦"],
// 2.数组: 存放的是对象
products: [
{ id: 110, name: "Macbook", price: 9.9, desc: "9.9秒杀, 快来抢购!" },
{ id: 111, name: "iPhone", price: 8.8, desc: "9.9秒杀, 快来抢购!" },
{ id: 112, name: "小米电脑", price: 9.9, desc: "9.9秒杀, 快来抢购!" },
]
}
},
})
// 2.挂载app
app.mount("#app")
</script>
</body>
</html>
1-基本使用
<ul>
<li v-for="movie in movies">{{ movie }}</li>
</ul>
谁用了v-for,谁就会被渲染出多个,所以这里是写到li里面而不是ul里面
2-不仅遍历数据,还遍历索引,用括号即可。
<ul>
<li v-for="(movie, index) in movies">{{index + 1}} - {{ movie }}</li>
</ul>
v-for渲染类型
v-for也可以遍历对象和字符串和数字
1-遍历对象,只写一个参数就是值value,写两个第二个是key,第三个是index索引(遍历数组的话,第一个是值,第二个是索引,没有键)
2-也可以遍历字符串,如abcd遍历在li,第一个li就是a,第二个li是b……
3-遍历数字的话,如上图二的代码,就是遍历1,2,3,4,5。。。。10
4-索引是从0开始的。
注意:v-for是写在需要遍历的标签上,如上是写在li而不是ul里面。
数组更新的检测
<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>
</head>
<body>
<div id="app">
<ul>
<li v-for="item in names">{{ item }}</li>
</ul>
<button @click="changeArray">修改数组</button>
</div>
<script src="../lib/vue.js"></script>
<script>
// 1.创建app
const app = Vue.createApp({
// data: option api
data() {
return {
names: ["abc", "cba", "nba", "aaa", "ccc"]
}
},
methods: {
changeArray() {
// 1.直接将数组修改为一个新的数组
// this.names = ["why", "kobe"]
// 2.通过一些数组的方法, 修改数组中的元素
// this.names.push("why")
// this.names.pop()
// this.names.splice(2, 1, "why")
// this.names.sort()
// this.names.reverse()
// 3.不修改原数组的方法是不能侦听(watch)
const newNames = this.names.map(item => item + "why")
this.names = newNames
}
}
})
// 2.挂载app
app.mount("#app")
</script>
</body>
</html>
也就是说,当我们使用v-for渲染一个数组中的内容,这个数组发生改变,那么渲染也会发生改变
但是如果是使用map这些方法,没有修改原数组,就不会被重新渲染
tips:
<li v-for="item in names">{{ item }}</li>
其中的in在vue3中新增了一个写法,就是把in写成of,效果和作用是一样的。
v-for的key属性
所以说key是用于在虚拟dom的diff算法中,来识别新老vnode的
注意:一般在v-for遍历的标签中加key属性,它和下面语句的key不同
<li v-for="(item,key,index) in object">{{ item }}</li>
一个是标签属性,一个是遍历对象里面的属性名
vnode并不是那个node环境,而是虚拟节点,它本质上是javascript对象
当我们在template中写了div这些元素代码,首先会形成一个vnode,它本质是一个对象,然后浏览器解析这些vnode对象就会形成一个虚拟dom,然后虚拟dom通过diff虚拟dom算法会决定哪些dom需要修改,哪些不需要,再生成真实dom
VNode是javascript对象,VNode表示Virtual DOM(虚拟DOM)中的虚拟节点
为什么不直接创建真实dom
1-方便diff算法
虚拟dom的diff会去比较新数据和老数据,遇到相同的就直接复用,不同的再重新生成虚拟dom,然后再生成真实dom
过程一:如果不设置key属性
如果我们是插入一个数据,而且不设置key属性,那么插入前的数据会复用,但是插入后的地方入上图插入f,会把原来c的vdom给f(li元素的dom不变,但是修改内容),然后原来d的vdom再给c,然后会发现原来e的vdom没位置了,就会再生成一个vnode来接收e;
那么明显这种算法的效率很低,如果是追加数据,相较于初数据,就在后面添加还好,如果 是插入的话,那么插入位置,原来的vdom全部要发生改变。 所以我们需要设置key属性
过程二:设置了key属性
key一般是绑定id,因为key是不能变的,是用来识别前后两次数据内容是否一样,如果key会变,那么它毫无意义
如果我们设置了key属性,那么abcde都有key,当我们插入f,abcde都会直接复用,在b后面再生成一个f的vnode即可。 可以大大节约性能
2-方便跨平台
因为虚拟dom树由一个个vnode组成,所以说它本质是一个js对象,我们可以把这个对象通过不同的解析方式在不同平台解析,可以在浏览器形成我们现在学的虚拟dom,也可以渲染到移动端上,而且不是以h5形式而是以原生的iOS或者安卓组件,甚至vr设备上。
options Api
optionsApi就是data和methods和template那些东西
computed计算属性使用
<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>
</head>
<body>
<div id="app">
<!-- 插值语法表达式直接进行拼接 -->
<!-- 1.拼接名字 -->
<h2>{{ firstName + " " + lastName }}</h2>
<h2>{{ firstName + " " + lastName }}</h2>
<h2>{{ firstName + " " + lastName }}</h2>
<!-- 2.显示分数等级 -->
<h2>{{ score >= 60 ? '及格': '不及格' }}</h2>
<!-- 3.反转单词显示文本 -->
<!-- 通过split把字符串以空格分开转化为数组,通过reverse反转数组,再通过join把数组变成字符串以空格隔开-->
<h2>{{ message.split(" ").reverse().join(" ") }}</h2>
</div>
<script src="../lib/vue.js"></script>
<script>
// 1.创建app
const app = Vue.createApp({
// data: option api
data() {
return {
// 1.姓名
firstName: "kobe",
lastName: "bryant",
// 2.分数: 及格/不及格
score: 80,
// 3.一串文本: 对文本中的单词进行反转显示
message: "my name is why"
}
},
})
// 2.挂载app
app.mount("#app")
</script>
</body>
</html>
方法一:抽取方法到methods中
<body>
<div id="app">
<!-- 插值语法表达式直接进行拼接 -->
<!-- 1.拼接名字 -->
<h2>{{ getFullname() }}</h2>
<h2>{{ getFullname() }}</h2>
<h2>{{ getFullname() }}</h2>
<!-- 2.显示分数等级 -->
<h2>{{ getScoreLevel() }}</h2>
<!-- 3.反转单词显示文本 -->
<h2>{{ reverseMessage() }}</h2>
</div>
<script src="../lib/vue.js"></script>
<script>
// 1.创建app
const app = Vue.createApp({
// data: option api
data() {
return {
// 1.姓名
firstName: "kobe",
lastName: "bryant",
// 2.分数: 及格/不及格
score: 80,
// 3.一串文本: 对文本中的单词进行反转显示
message: "my name is why"
}
},
methods: {
getFullname() {
return this.firstName + " " + this.lastName
},
getScoreLevel() {
return this.score >= 60 ? "及格": "不及格"
},
reverseMessage() {
return this.message.split(" ").reverse().join(" ")
}
}
})
// 2.挂载app
app.mount("#app")
</script>
</body>
split是把字符串按照设置字符分割为数组;join是把数组变成字符串,通过设置的字符分隔。
return this.message.split(“ “).reverse().join(“ “)
为什么要转换为数组?
因为reserve方法是给数组用的,字符串用不了
方法二:使用计算属性computed
响应式数据通俗说,就是用了data里面的数据; computed也属于optionsApi
computed里面和methods一样也可以直接通过this来使用data里面返回的变量(因为vue底层通过bind等重定向了this)
<body>
<div id="app">
<!-- 1.methods -->
<h2>{{ getFullname() }}</h2>
<h2>{{ getFullname() }}</h2>
<h2>{{ getFullname() }}</h2>
<!-- 2.computed -->
<h2>{{ fullname }}</h2>
<h2>{{ fullname }}</h2>
<h2>{{ fullname }}</h2>
<!-- 修改name值 -->
<button @click="changeLastname">修改lastname</button>
</div>
<script src="../lib/vue.js"></script>
<script>
// 1.创建app
const app = Vue.createApp({
// data: option api
data() {
return {
firstName: "kobe",
lastName: "bryant"
}
},
methods: {
getFullname() {
console.log("getFullname-----")
return this.firstName + " " + this.lastName
},
changeLastname() {
this.lastName = "why"
}
},
computed: {
fullname() {
console.log("computed fullname-----")
return this.firstName + " " + this.lastName
}
}
})
// 2.挂载app
app.mount("#app")
</script>
</body>
我们乍一看,好像和写在methods里面的方法没什么区别呀?
1-写在computed可以更易读,数据处理就在conputed,触发事件就在methods
2-computed写在大胡子语法里面,不需要加() 。
computed和methods区别
computed和methods最大的区别是,computed是有缓存的。
也就是说,如果我们用methods中的函数来显示数据,不同的地方用了多少次这个数据,这个函数就会被调用多少次。
但是如果用computed那么只要一个地方使用了,运行一次函数,其他地方再使用,将不会再运行函数,而是直接用缓存去显示(vue底层自己的优化),只有数据发生变化,才会再次调用函数。
computed的set和get
下面是计算属性的完整写法,一般开发很少这样写。
<body>
<div id="app">
<h2>{{ fullname }}</h2>
<button @click="setFullname">设置fullname</button>
</div>
<script src="../lib/vue.js"></script>
<script>
// 1.创建app
const app = Vue.createApp({
// data: option api
data() {
return {
firstname: "coder",
lastname: "why"
}
},
computed: {
// 语法糖的写法
// fullname() {
// return this.firstname + " " + this.lastname
// },
// 完整的写法:
fullname: {
get: function() {
return this.firstname + " " + this.lastname
},
set: function(value) {
const names = value.split(" ")
this.firstname = names[0]
this.lastname = names[1]
}
}
},
methods: {
setFullname() {
this.fullname = "kobe bryant"
}
}
})
// 2.挂载app
app.mount("#app")
</script>
</body>
完整写法下,其实是写成一个对象,如上代码,当我们通过this.fullname给他赋值的时候,这个值实际上去到了set方法的value中(在运行的时候,只执行get一遍,当我们触发上面代码的设置fullname值的事件,会执行一遍set,再执行一遍get)
如下代码,如果computed没有set属性,那么 this.fullname就是get返回的值
this.fullname = "kobe bryant"
如果我们人为设置了this.fullname属性的值,那么这个值就会变成set属性对应函数的参数传递到set内(set属性对应的函数的语句是自己编写的,我们如果自己不写set,也就不会去设置fullname属性的值)
在实际开发中,我们都不用set,所以get和set都可以省略成一个语法糖的形式,如fullname(){return this.firstname + “ “ + this.lastname}
为什么在使用的时候可以直接写fullname使用,不需要加括号调用呢?,如
<h2>{{ fullname }}</h2>
可能是get方法有内置的一些机制。(现在我们记住就好了,后续会研究底层)
为什么computed里面的“函数”在插值语法里面不要想methods函数一样写小括号呢?
因为我们开发中一般不设置set,那么其实就只有一个get属性对应的函数,js对象的高级写法就可以省略成语法糖的样子,只是进行了两重省略。平时对象的高级写法只省略了一次。
侦听器watch选项使用
<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>
</head>
<body>
<div id="app">
<h2>{{message}}</h2>
<button @click="changeMessage">修改message</button>
</div>
<script src="../lib/vue.js"></script>
<script>
// Proxy -> Reflect
// 1.创建app
const app = Vue.createApp({
// data: option api
data() {
return {
message: "Hello Vue",
info: { name: "why", age: 18 }
}
},
methods: {
changeMessage() {
this.message = "你好啊, 李银河!"
this.info = { name: "kobe" }
}
},
watch: {
// 1.默认有两个参数: newValue/oldValue
message(newValue, oldValue) {
console.log("message数据发生了变化:", newValue, oldValue)
},
info(newValue, oldValue) {
// 2.如果是对象类型, 那么拿到的是代理对象
// console.log("info数据发生了变化:", newValue, oldValue)
// console.log(newValue.name, oldValue.name)
// 3.获取原生对象
// console.log({ ...newValue })
console.log(Vue.toRaw(newValue))
}
}
})
// 2.挂载app
app.mount("#app")
</script>
</body>
</html>
1-如上代码,比如监听data中的message,我们就在watch对象中写一个message(){},你侦听哪个数据a就把函数写成什么名字; 别问为什么,底层就是这样设置的,得去研究源码。
当message发生改变,那么就会自动执行watch中的message()函数
2-如果我们要获取到数据改变的数据和当前最新的数据,可以写两个参数,newValue和oldValue(随便命名,第一个是新数据,第二个是老数据)
watch中的immediate和deep
<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>
</head>
<body>
<div id="app">
<h2>{{ info.name }}</h2>
<button @click="changeInfo">修改info</button>
</div>
<script src="../lib/vue.js"></script>
<script>
// 1.创建app
const app = Vue.createApp({
// data: option api
data() {
return {
info: { name: "why", age: 18 }
}
},
methods: {
changeInfo() {
// 1.创建一个新对象, 赋值给info
// this.info = { name: "kobe" }
// 2.直接修改原对象某一个属性
this.info.name = "kobe"
}
},
watch: {
// 默认watch监听不会进行深度监听
// info(newValue, oldValue) {
// console.log("侦听到info改变:", newValue, oldValue)
// }
// 进行深度监听
info: {
handler(newValue, oldValue) {
console.log("侦听到info改变:", newValue, oldValue)
console.log(newValue === oldValue)
},
// 监听器选项:
// info进行深度监听
deep: true,
// 第一次渲染直接执行一次监听器
immediate: true
},
//下面是vue2写法,了解一下
"info.name": function(newValue, oldValue) {
console.log("name发生改变:", newValue, oldValue)
}
}
})
// 2.挂载app
app.mount("#app")
</script>
</body>
</html>
1-如上的监听info,如果我们不需要配置什么的,直接写info(){}即可,这是完整写法的语法糖
2-但是不配置,默认其实是浅监听,也就是说当上面代码info对象没有重新赋值,只是修改了里面的某个属性,是监听不到的,我们需要通过完整写法,设置deep:true
3-完整写法中有一个handler函数(不写完整写法,那么这个handler函数就通过对象的高级写法省略了,如果写完整写法,那么这个函数必须叫handler这是vue底层设置的,其实handler也是一个对象的高级写法,已经省略一次了),immediate配置是让第一次渲染直接执行一次监听器
监听器的其他写法
了解即可,很少很少使用
阶段性综合案例
这种综合案例和我们实际开发中的项目都是一样的,我们需要先写出页面,然后再去考虑逻辑,添加事件等等
注意我们要去使用vue,不要自己习惯性的去操作dom
<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>
<style>
table {
border-collapse: collapse;
/* text-align: center; */
}
thead {
background-color: #f5f5f5;
}
th, td {
border: 1px solid #aaa;
padding: 8px 16px;
}
.active {
background-color: skyblue;
}
</style>
</head>
<body>
<div id="app">
<!-- 1.搭建界面内容 -->
<template v-if="books.length">
<table>
<thead>
<tr>
<th>序号</th>
<th>书籍名称</th>
<th>出版日期</th>
<th>价格</th>
<th>购买数量</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<!-- 注意这里是如何绑定id的,key也是一个属性,所以需要v-bind -->
<tr v-for="(item, index) in books"
:key="item.id"
@click="rowClick(index)"
:class="{ active: index === currentIndex }">
<td>{{ index + 1 }}</td>
<td>{{ item.name }}</td>
<td>{{ item.date }}</td>
<td>{{ formatPrice(item.price) }}</td>
<td>
<button :disabled="item.count <= 1" @click="decrement(index, item)">-</button>
{{ item.count }}
<button @click="increment(index, item)">+</button>
</td>
<td>
<button @click="removeBook(index, item)">移除</button>
</td>
</tr>
</tbody>
</table>
<h2>总价: {{ formatPrice(totalPrice) }}</h2>
</template>
<template v-else>
<h1>购物车为空, 请添加喜欢的书籍~</h1>
<p>商场中有大量的IT类的书籍, 请选择添加学习, 注意保护好自己的头发!</p>
</template>
</div>
<script src="../lib/vue.js"></script>
<script src="./data/data.js"></script>
<script>
// 1.创建app
const app = Vue.createApp({
// data: option api
data() {
return {
books: books,
currentIndex: 0
}
},
// computed
computed: {
totalPrice() {
// 1.直接遍历books
// let price = 0
// for (const item of this.books) {
// price += item.price * item.count
// }
// return price
// 2.reduce(自己决定,遍历方法很多种,选你最熟练的)
return this.books.reduce((preValue, item) => {
return preValue + item.price * item.count
}, 0)
}
},
methods: {
//这里是格式化价格,就是把所有价格前加一个¥
//我们也可以直接把¥写在template里面,但是需要写很多个
//当某一天这个¥要变成$,那么就更难改了,如果我们封装一个方法,那么就只需要改下面一处
formatPrice(price) {
return "¥" + price
},
// 监听-和+操作
decrement(index, item) {
console.log("点击-")
// this.books[index].count--
item.count--
},
increment(index, item) {
console.log("点击+:", index)
// this.books[index].count++
item.count++
},
removeBook(index, item) {
this.books.splice(index, 1)
},
rowClick(index) {
this.currentIndex = index
}
}
})
// 2.挂载app
app.mount("#app")
</script>
</body>
</html>
data.js
const books = [
{
id: 1,
name: '《算法导论》',
date: '2006-9',
price: 85.00,
count: 1
},
{
id: 2,
name: '《UNIX编程艺术》',
date: '2006-2',
price: 59.00,
count: 1
},
{
id: 3,
name: '《编程珠玑》',
date: '2008-10',
price: 39.00,
count: 1
},
{
id: 4,
name: '《代码大全》',
date: '2006-3',
price: 128.00,
count: 1
},
{
id: 5,
name: '《你不知道JavaScript》',
date: '2014-8',
price: 88.00,
count: 1
},
]