Vue.js魔法书:前端开发者的终极指南----指令篇续篇

​个人名片:
😊作者简介:一个为了让更多人看见许舒雅的宝贝的小白先生
🤡个人主页:🔗 许舒雅的宝贝
🐼座右铭:深夜两点半的夜灯依旧闪烁,凌晨四点的闹钟不止你一个。
🎅学习目标: 坚持前端的学习进度,做一个全栈开发工程师

上篇文章没有写完的内容,在此进行补充。

目录

🌟 1.指令修饰符

👉 1.1按键修饰符

👉 1.2v-model修饰符

 👉 1.3补充

🌟 2.v-bind对样式控制的增强-操作class

👉 2.1数组语法

👉 2.2对象语法

 ✍🏻 小案例:京东秒杀-tab栏切换导航高亮

🌟 3.v-bind对样式控制的增强---操作style

👉 3.1语法

👉 3.2代码练习

✍🏻 小案例:进度条案例

🌟 4.v-model在其他表单元素的使用

🌟 5.computed计算属性

👉 5.1概念

👉 5.2语法

👉 5.3注意

👉 5.4案例

🌟 6.computed计算属性和methods方法的区别

👉 6.1computed计算属性

👉 6.2methods计算属性

👉 6.3计算属性的优势

👉 6.4总结

🌟 7.计算属性的完整写法

✍🏻 综合案例:成绩单

🌟 8.watch侦听器

👉 8.1作用

👉 8.2语法

🎉 8.2.1简单写法案例1

🎉 8.2.2简单写法案例2

🎉 8.2.3完整写法案例1(deep的使用)

 🎉 8.2.4完整写法案例2(immediate的使用)

👉 8.3总结

✍🏻 综合案例:购物车案例


🌟 1.指令修饰符

在Vue中,指令修饰符是用来改变指令行为的特殊后缀,通过在指令名称后添加一个点(.)来使用。用来帮助开发者简化判断代码。

👉 1.1按键修饰符

①@keyup.enter:键盘回车监听

②@keyup.tab:触发当用户按下tab键

③@keyup.delete或@keyup.esc:触发当用户按下Delete键(捕获“删除”键和Esc键)

等等,还有.space,up,down,left,right等等修饰符,最常用的已经给大家标注出来啦。


<!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" /><link rel="stylesheet" href="./css/style.css" /><title>记事本</title>
</head>
<body><!-- 主体区域 -->
<section id="app"><!-- 输入框 --><header class="header"><h1>小黑记事本</h1><input @keyup.enter="addTask()" v-model="message" placeholder="请输入任务" class="new-todo" /><button class="add" @click="addTask()">添加任务</button></header><!-- 列表区域 --><section class="main"><ul class="todo-list"><li class="todo" v-for="(item,index) in taskList" :key="item.id"><div class="view"><span class="index">{{ index + 1 }}.</span> <label>{{ item.msg }}</label><button class="destroy" @click="del(item.id)"></button></div></li></ul></section><!-- 统计和清空   如果没有任务了,底部隐藏掉--><footer class="footer" v-show="taskList.length > 0"><!-- 统计 --><span class="todo-count">合 计:<strong> {{ taskList.length }} </strong></span><!-- 清空 --><button class="clear-completed" @click="reset()">清空任务</button></footer>
</section><!-- 底部 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {message: '',taskList: [{id: 1, msg: '吃饭'},{id: 2, msg: '跑步'},{id: 3, msg: '睡觉'},]},methods: {del(id){this.taskList = this.taskList.filter(item => item.id !== id)},reset(){this.taskList = []},addTask() {// trim()函数去除字符串左右两边的空格,不去除字符串中的空格if(this.message.trim() === ''){alert('请输入任务内容')return}this.taskList.unshift({id: +new Date(),msg: this.message})this.message = ''}},})</script>
</body>
</html>

用法就以keyup.enter作为案例来演示一下,这个很好理解,就是在某个地方我们使用了按键修饰符,那么当我们在这个地方使用键盘的按键之后,就会触发绑定的事件,就这么简单。

ok,我们现在思考一下怎么实现的?

当我们按下enter的时候,我们是不是可以拿到这个事件对象,事件对象中是不是有一个key,根据这个key来判断是否去触发事件不就行了。

我们可以在触发方法中加一行代码:console.log(e),方法参数传值e,上篇文章我们说了的,方法可以直接获取事件对象e。在页面F12查看控制台,触发事件观察事件对象的key。

👉 1.2v-model修饰符

①v-model.lazy:默认情况下,v-model在input事件中同步输入框的值与数据,使用.lazy修饰后,v-model则会变成在change事件中同步。

②v-model.number:当用户输入值时,.number修饰符确保输入值被转换为数值类型,如果输入值无法转换为数值,则为字符串。

③v-model.trim:使用.trim修饰符,会自动过滤用户输入的首尾空白字符。这里的.trim和和函数.trim()是不一样的,不要弄混了,虽然功能一样。

🚨 注意:这些修饰符都必须要和v-model结合使用,以满足不同的数据绑定需求。

 👉 1.3补充

①@事件名.stop:阻止冒泡,冒泡的意思就是说比如父div包裹了一个子div,父子都有自己的触发事件,当我们点击子div的事件的时候,会出现冒泡到了父div。

②事件名.prevent:阻止默认行为,比如我们有a标签,这个是跳转链接的标签,当我们在这个标签上使用,就无法跳转到该链接。
🚨 注意:@事件名.stop.prevent —>可以连用 即阻止事件冒泡也阻止默认行为

<style>.father {width: 200px;height: 200px;background-color: pink;margin-top: 20px;}.son {width: 100px;height: 100px;background-color: skyblue;}</style><div id="app"><h3>v-model修饰符 .trim .number</h3>姓名:<input v-model="username" type="text"><br>年纪:<input v-model="age" type="text"><br><h3>@事件名.stop     →  阻止冒泡</h3><div @click="fatherFn" class="father"><div @click="sonFn" class="son">儿子</div></div><h3>@事件名.prevent  →  阻止默认行为</h3><a @click href="http://www.baidu.com">阻止默认行为</a></div><script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script><script>const app = new Vue({el: '#app',data: {username: '',age: '',},methods: {fatherFn () {alert('老父亲被点击了')},sonFn (e) {// e.stopPropagation()alert('儿子被点击了')}}})</script>

🌟 2.v-bind对样式控制的增强-操作class

为了方便开发者进行样式控制, Vue 扩展了 v-bind 的语法,可以针对 class 类名 和 style 行内样式进行控制 。

👉 2.1数组语法

<div> :class = "对象/数组">这是一个div</div>

<!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" /><link rel="stylesheet" href="./css/style.css" /><title>demo</title>
</head>
<style>.box {width: 200px;height: 200px;background-color: #fff;border: 1px solid #000;}.pink {background-color: pink;}.big {width: 500px;height: 500px;}
</style>
<body><div id="app">
<!--        <div class="box" :class="{pink: true}">小白先生</div>--><div class="box" :class="['pink']">小白先生</div></div>
<!-- 底部 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {}})</script>
</body>
</html>

 测试结果

适用场景:批量添加或删除类

👉 2.2对象语法

当class动态绑定的是对象时,就是类名,值就是布尔值,如果值是true,就有这个类,否则没有这个类

<div class="box" :class="{ 类名1: 布尔值, 类名2: 布尔值 }"></div>

<!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" /><link rel="stylesheet" href="./css/style.css" /><title>demo</title>
</head>
<style>.box {width: 200px;height: 200px;background-color: #fff;border: 1px solid #000;}.pink {background-color: pink;}.big {width: 500px;height: 500px;}
</style>
<body><div id="app">
<!--        <div class="box" :class="{pink: true}">小白先生</div>--><div class="box" :class="{pink: true,big: true}">小白先生</div></div>
<!-- 底部 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {}})</script>
</body>
</html>

测试结果

 适用场景:一个类名,来回切换

 ✍🏻 小案例:京东秒杀-tab栏切换导航高亮

需求:当我们点击哪个导航栏时,哪个导航栏高亮。


<!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" /><link rel="stylesheet" href="./css/style.css" /><title>demo</title>
</head>
<style>* {margin: 0;padding: 0;}ul {display: flex;border-bottom: 2px solid #e01222;padding: 0 10px;}li {width: 100px;height: 50px;line-height: 50px;list-style: none;text-align: center;}li a {display: block;text-decoration: none;font-weight: bold;color: #333333;}li a.active {background-color: #e01222;color: #fff;}</style>
<body>
<div id="app"><ul><li v-for="(item,index) in list" :key="item.id"><a :class="{active:index===activeIndex}" @click="activeIndex = index " href="#">{{item.name}}</a></li></ul>
</div>
<!-- 底部 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {activeIndex: 0,list: [{ id: 1, name: '京东秒杀' },{ id: 2, name: '每日特价' },{ id: 3, name: '品类秒杀' }]}})</script>
</body>
</html>

测试结果: 

思路很简单:

①根据数据适用v-for动态渲染数据

②准备一个下标,记录高亮的是哪一个

③基于下标动态切换class的类名 

🌟 3.v-bind对样式控制的增强---操作style

👉 3.1语法

<div class="box" :style="{ CSS属性名1: CSS属性值, CSS属性名2: CSS属性值 }"></div>

👉 3.2代码练习


<!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" /><link rel="stylesheet" href="./css/style.css" /><title>demo2</title>
</head>
<style>.box {width: 200px;height: 200px;background-color: rgb(187, 150, 156);}
</style>
<body>
<div id="app"><div class="box" :style="{width: '400px'}"></div>
</div>
<!-- 底部 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {}})</script>
</body>
</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" /><link rel="stylesheet" href="./css/style.css" /><title>demo2</title>
</head>
<style>.progress {height: 25px;width: 400px;border-radius: 15px;background-color: #272425;border: 3px solid #272425;box-sizing: border-box;margin-bottom: 30px;}.inner {width: 50%;height: 20px;border-radius: 10px;text-align: right;position: relative;background-color: #409eff;background-size: 20px 20px;box-sizing: border-box;transition: all 1s;}.inner span {position: absolute;right: -20px;bottom: -25px;}
</style>
<body>
<div id="app"><div class="progress"><div class="inner" :style="{width:progress+'%'}"><span>{{progress}}%</span></div></div><button @click="changeProgress(25)">设置25%</button><button @click="changeProgress(50)">设置50%</button><button @click="changeProgress(75)">设置75%</button><button @click="changeProgress(100)">设置100%</button>
</div>
<!-- 底部 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {progress:25},methods: {changeProgress(num){this.progress = num}}})</script>
</body>
</html>

测试结果:

🌟 4.v-model在其他表单元素的使用

常见的表单元素都可以用v-model绑定关联-->快速获取或设置表单元素的值,它会根据控件类型自动选取正确的方法来更新元素。

输入框  input:text   ——> value
文本域  textarea	 ——> value
复选框  input:checkbox  ——> checked
单选框  input:radio   ——> checked
下拉菜单 select    ——> value
...
<!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" /><link rel="stylesheet" href="./css/style.css" /><title>demo</title>
</head>
<body>
<div id="app"><h3>个人信息主页</h3>姓名:<input type="text" v-model="username"><br><br>是否单身:<input type="checkbox" v-model="isSingle"><br><br><!--前置理解:1. name:  给单选框加上 name 属性 可以分组 → 同一组互相会互斥2. value: 给单选框加上 value 属性,用于提交给后台的数据并且通常value用具体的标识,一般不会传字符串文本的,一般是男是1,女是2      结合 Vue 使用 → v-model-->性别:<input v-model="gender" type="radio" name="gender" value="1">男<input v-model="gender" type="radio" name="gender" value="2">女<br><br><!--前置理解:1. option 需要设置 value 值,提交给后台option的value里面城市的编码一般是从后台获取的2. select 的 value 值,关联了选中的 option 的 value 值结合 Vue 使用 → v-model-->所在城市:<select v-model="cityId"><option value="101">北京</option><option value="102">上海</option><option value="103">成都</option><option value="104">南京</option></select><br><br><!-- 文本域和输入框是类似的 -->自我描述:<textarea v-model="desc"></textarea><button>立即注册</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {username: '',isSingle: false,// 标识女选中gender: "2",cityId: '102',desc: ""}})
</script>
</body></html>

测试结果:

🌟 5.computed计算属性

👉 5.1概念

基于现有的数据,计算出新属性依赖的数据变化,自动重新计算。

👉 5.2语法

①声明在computed配置项中,一个计算属性对应一个函数

②使用起来和普通属性一样使用{{计算属性名}}

👉 5.3注意

①computed配置项和data配置项是同级

②computed中的计算属性虽然是函数的写法,但他依然是个属性

③computed中的计算属性不能和data中的属性同名

④使用computed中的计算属性和使用data中的属性是一样的用法

⑤computed中计算属性内部的this依然指向的是Vue实例

👉 5.4案例

<!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" /><link rel="stylesheet" href="./css/style.css" /><title>demo</title>
</head><style>table {border: 1px solid #000;text-align: center;width: 240px;}th,td {border: 1px solid #000;}h3 {position: relative;}
</style>
<body>
<div id="app"><h3>小黑的礼物清单</h3><table><tr><th>名字</th><th>数量</th></tr><tr v-for="(item, index) in list" :key="item.id"><td>{{ item.name }}</td><td>{{ item.num }}个</td></tr></table><!-- 目标:统计求和,求得礼物总数 --><p>礼物总数:{{total}} 个</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {// 现有的数据list: [{ id: 1, name: '篮球', num: 1 },{ id: 2, name: '玩具', num: 2 },{ id: 3, name: '铅笔', num: 5 },]},computed: {total(){let sum = 0;this.list.forEach(item => {sum += item.num;});return sum;}}})
</script>
</body></html>

 测试结果:

🌟 6.computed计算属性和methods方法的区别

👉 6.1computed计算属性

作用:封装了一段对于数据的处理,求得一个结果

语法:

①写在computed配置项中

②作为属性,直接使用

        Ⅰjs中使用计算属性:this.计算属性

        Ⅱ模板中使用计算属性:{{计算属性}}

👉 6.2methods计算属性

作用:给Vue实例提供一个方法,调用以处理业务逻辑

语法:

①写在methods配置项中

②作为方法调用

        Ⅰjs中调用:this.方法名()

        Ⅱ模板中调用{{方法名()}}或者@事件名="方法名"

👉 6.3计算属性的优势

①缓存特性(提升性能):计算属性会对计算出来的结果缓存,在此使用时直接读取缓存,依赖项变化了,会自动重新计算,并再次缓存。

②methods没有缓存特性

👉 6.4总结

①computed有缓存特性,methods没有缓存   ②当一个结果依赖其他多个值时,推荐使用计算属性

③当处理业务逻辑时,推荐使用methods方法,比如事件的处理函数

🌟 7.计算属性的完整写法

计算属性也是属性,能访问,应该也是可以修改的

计算属性默认的简写,只能读取访问,不能“修改”,如果需要修改,需要写计算属性的完整写法。


<!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" /><link rel="stylesheet" href="./css/style.css" /><title>demo</title>
</head><body>
<div id="app">姓:<input type="text" v-model="firstName"> +名:<input type="text" v-model="lastName"> =<span>{{totalName}}</span><br><br><button @click="changeName">改名卡</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {firstName: '刘',lastName: '备'},computed: {totalName:{// (1) 当fullName计算属性,被获取求值时,执行get(有缓存,优先读缓存)//     会将返回值作为,求值的结果get(){return this.firstName + this.lastName},// (2) 当fullName计算属性,被修改赋值时,执行set//     修改的值,传递给set方法的形参set(value){this.firstName = value.slice(0,1)this.lastName = value.slice(1)},}},methods: {changeName(){this.totalName = '吕小布'}}})
</script>
</body></html>

computed的完整写法一般不常用,一般只作为计算,最终展示在页面上,不做修改操作,因为可以看出来,修改的操作还挺麻烦的。

✍🏻 综合案例:成绩单

功能描述:

1.渲染功能  2.删除功能  3.添加功能  4.统计总分,求平均分

思路分析:

1.渲染功能 v-for :key v-bind:动态绑定class的样式

2.删除功能 v-on绑定事件, 阻止a标签的默认行为

3.v-model的修饰符 .trim、 .number、 判断数据是否为空后 再添加、添加后清空文本框的数据

4.使用计算属性computed 计算总分和平均分的值

<!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" /><link rel="stylesheet" href="./css/index.css" /><title>Document</title>
</head>
<body>
<div id="app" class="score-case"><div class="table"><table><thead><tr><th>编号</th><th>科目</th><th>成绩</th><th>操作</th></tr></thead><tbody><tr v-for="(item,index) in list" :key="item.id"><td>{{index+1}}</td><td>{{ item.subject }}</td><td :class="{red:item.score<60}">{{ item.score }}</td><td><a @click.prevent="deleteData(item.id)" href="#">删除</a></td></tr></tbody><tbody v-if="list.length<=0"><tr><td colspan="5"><span class="none">暂无数据</span></td></tr></tbody><tfoot><tr><td colspan="5"><span>总分:{{ totalScope }}</span><span style="margin-left: 50px">平均分:{{ average }}</span></td></tr></tfoot></table></div><div class="form"><div class="form-item"><div class="label">科目:</div><div class="input"><inputtype="text"placeholder="请输入科目"v-model.trim="subject"/></div></div><div class="form-item"><div class="label">分数:</div><div class="input"><inputtype="text"placeholder="请输入分数"v-model.trim.number="score"/></div></div><div class="form-item"><div class="label"></div><div class="input"><button class="submit" @click="addData()">添加</button></div></div></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {list: [{ id: 1, subject: '语文', score: 20 },{ id: 7, subject: '数学', score: 99 },{ id: 12, subject: '英语', score: 70 },],subject: '',score: ''},methods: {deleteData(id){this.list = this.list.filter(item => item.id !== id)},addData(){if(!this.subject || !this.score){alert('请输入数据!输入框不可为空!')return}//unshift将元素把到数组的最前面,后面的元素一次往后移一位this.list.unshift({id: +new Date(),subject: this.subject,score: this.score})this.subject = ''this.score = ''}},computed: {totalScope(){return this.list.reduce((prev,item) => prev + Number(item.score),0)},average(){return this.list.reduce((prev,item) => prev + Number(item.score),0) / this.list.length}}})
</script>
</body>
</html>

样式文件css:

.score-case {width: 1000px;margin: 50px auto;display: flex;
}
.score-case .table {flex: 4;
}
.score-case .table table {width: 100%;border-spacing: 0;border-top: 1px solid #ccc;border-left: 1px solid #ccc;
}
.score-case .table table th {background: #f5f5f5;
}
.score-case .table table tr:hover td {background: #f5f5f5;
}
.score-case .table table td,
.score-case .table table th {border-bottom: 1px solid #ccc;border-right: 1px solid #ccc;text-align: center;padding: 10px;
}
.score-case .table table td.red,
.score-case .table table th.red {color: red;
}
.score-case .table .none {height: 100px;line-height: 100px;color: #999;
}
.score-case .form {flex: 1;padding: 20px;
}
.score-case .form .form-item {display: flex;margin-bottom: 20px;align-items: center;
}
.score-case .form .form-item .label {width: 60px;text-align: right;font-size: 14px;
}
.score-case .form .form-item .input {flex: 1;
}
.score-case .form .form-item input,
.score-case .form .form-item select {appearance: none;outline: none;border: 1px solid #ccc;width: 200px;height: 40px;box-sizing: border-box;padding: 10px;color: #666;
}
.score-case .form .form-item input::placeholder {color: #666;
}
.score-case .form .form-item .cancel,
.score-case .form .form-item .submit {appearance: none;outline: none;border: 1px solid #ccc;border-radius: 4px;padding: 4px 10px;margin-right: 10px;font-size: 12px;background: #ccc;
}
.score-case .form .form-item .submit {border-color: #069;background: #069;color: #fff;
}

测试结果:

 这个代码可能不是最简单的和简短的,我只是为了练习之前学的一些内容,所以我是按照我的学习进度写的代码,大家可能有更好的解决方案的。

🌟 8.watch侦听器

👉 8.1作用

监视数据变化,执行一些业务逻辑或异步操作。

👉 8.2语法

①watch声明在data同级的配置项中

②简单写法:简单类型数据直接监视

③完整写法:添加额外的配置项

<script>const app = new Vue({el: "#app",data: {project: "语文",obj: {name: "张三",},},watch: {//该方法会在数据变化,触发执行数据属性名(newValue, oldValue) {一些业务逻辑或异步操作;},"对象.属性名"(newValue, oldValue) {一些业务逻辑或异步操作;},},});</script>

🎉 8.2.1简单写法案例1

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title>
</head>
<body>
<div id="app"><textarea name="" v-model="project"></textarea>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>new Vue({el: "#app",data: {project: "语文",},watch: {//该方法会在数据变化,触发执行//newValue新值,oldValue老值(一般不用)project(newValue, oldValue) {console.log("变化前的值:", newValue, "变化后的值:", oldValue);},},});
</script>
</html>

测试结果:

🎉 8.2.2简单写法案例2

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title>
</head>
<body>
<div id="app"><textarea name="" v-model="obj.project"></textarea>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>new Vue({el: "#app",data: {obj: {project: "语文"}},watch: {//该方法会在数据变化,触发执行//newValue新值,oldValue老值(一般不用)"obj.project"(newValue, oldValue) {console.log("变化前的值:", newValue, "变化后的值:", oldValue);},},});
</script>
</html>

测试结果:

🎉 8.2.3完整写法案例1(deep的使用)

1.deep:true对复杂类型深度监视(监视对象中的所有属性)

2.immediate:true 初始化立刻执行一次handler方法

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title>
</head>
<body>
<div id="app"><select v-model="obj.country"><option value="italy">意大利</option><option value="english">英语</option><option value="german">德语</option></select><textarea name="" v-model="obj.project"></textarea>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>const app = new Vue({el: "#app",data: {obj: {project: "语文",country: "english",},},watch: {//完整写法不是一个方法,是一个对象obj: {deep: true,handler(newValue) {console.log("对象被改变了", newValue);},},},});
</script>
</html>

测试结果:

 🎉 8.2.4完整写法案例2(immediate的使用)

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title>
</head>
<body>
<div id="app"><select v-model="obj.country"><option value="italy">意大利</option><option value="english">英语</option><option value="german">德语</option></select><textarea name="" v-model="obj.project"></textarea>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>const app = new Vue({el: "#app",data: {obj: {project: "语文",country: "english",},},watch: {//完整写法不是一个方法,是一个对象obj: {deep: true,immediate: true,handler(newValue) {console.log("对象被改变了", newValue);},},},});
</script>
</html>

测试结果: 

👉 8.3总结

watch侦听器的写法有几种?

简单写法

watch: {数据属性名 (newValue, oldValue) {一些业务逻辑 或 异步操作。 },'对象.属性名' (newValue, oldValue) {一些业务逻辑 或 异步操作。 }
}

完整写法

watch: {// watch 完整写法数据属性名: {deep: true, // 深度监视(针对复杂类型)immediate: true, // 是否立刻执行一次handlerhandler (newValue) {console.log(newValue)}}
}

✍🏻 综合案例:购物车案例

需求说明:

  1. 渲染功能

  2. 删除功能

  3. 修改个数

  4. 全选反选

  5. 统计 选中的 总价 和 总数量

  6. 持久化到本地

<!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" /><link rel="stylesheet" href="./css/inputnumber.css" /><link rel="stylesheet" href="./css/index.css" /><title>购物车</title>
</head><body>
<div class="app-container" id="app"><!-- 顶部banner --><div class="banner-box"><img src="./img/fruit.jpg" alt="" /></div><!-- 面包屑 --><div class="breadcrumb"><span>🏠</span>/<span>购物车</span></div><!-- 购物车主体 --><div v-if="fruitList.length > 0" class="main"><div class="table"><!-- 头部 --><div class="thead"><div class="tr"><div class="th">选中</div><div class="th th-pic">图片</div><div class="th">单价</div><div class="th num-th">个数</div><div class="th">小计</div><div class="th">操作</div></div></div><!-- 身体 --><div class="tbody"><div v-for="(item,index) in fruitList" :key="item.id" class="tr" :class="{active: item.isChecked}"><div class="td"><input type="checkbox" v-model="item.isChecked" /></div><div class="td"><img :src="item.icon" alt="" /></div><div class="td">{{ item.price }}</div><div class="td"><div class="my-input-number"><button :disabled="item.num < 1 " @click="sub(item.id)" class="decrease"> - </button><span class="my-input__inner">{{ item.num }}</span><button @click="add(item.id)" class="increase"> + </button></div></div><div class="td">{{ item.num*item.price}}</div><div class="td"><button @click="del(item.id)">删除</button></div></div></div></div><!-- 底部 --><div class="bottom"><!-- 全选 --><label class="check-all"><input v-model="isAll" type="checkbox" />全选</label><div class="right-box"><!-- 所有商品总价 --><span class="price-box">总价&nbsp;&nbsp;:&nbsp;&nbsp;¥&nbsp;<span class="price">{{totalPrice}}</span></span><!-- 结算按钮 --><button class="pay">结算( {{ totalCount }} )</button></div></div></div><!-- 空车 --><div v-else class="empty">🛒空空如也</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>const defaultArr = [{id: 1,icon: './img/火龙果.png',isChecked: true,num: 2,price: 6,},{id: 2,icon: './img/荔枝.png',isChecked: false,num: 7,price: 20,},{id: 3,icon: './img/榴莲.png',isChecked: false,num: 3,price: 40,},{id: 4,icon: './img/鸭梨.png',isChecked: true,num: 10,price: 3,},{id: 5,icon: './img/樱桃.png',isChecked: false,num: 20,price: 34,},]const app = new Vue({el: '#app',data: {// 水果列表fruitList: JSON.parse(localStorage.getItem('list')) || defaultArr,},computed: {isAll: {//获取计算属性get() {return this.fruitList.every(item => item.isChecked === true);},//设置计算属性并下发给小选项//当你直接对复选框进行修改时,即修改计算属性,那么修改的值会传入到set的形参当中set(value) {this.fruitList.forEach(item => item.isChecked = value);}},//统计水果总价totalPrice() {return this.fruitList.reduce((sum, item) => {if (item.isChecked) {sum = sum + item.num*item.price;return sum;} else {return sum;}}, 0)},//统计水果总数量totalCount() {return this.fruitList.reduce((sum, item) => {if (item.isChecked) {sum = sum + item.num;return sum;} else {return sum;}}, 0)}},methods: {//删除某一行水果del: function (id) {this.fruitList = this.fruitList.filter(item => item.id != id)},//将某一行的水果的数量增加add: function (id) {//1.找到对应id的水果const fruit = this.fruitList.find(item => item.id === id);//2.添加水果个数fruit.num++},//减少水果数量sub: function (id) {//1.找到对应id的水果const fruit = this.fruitList.find(item => item.id === id);//2.减少水果个数fruit.num--}},watch:{fruitList:{deep:true,handler(newValue){//将变化后的newValue存入本地localStorage.setItem('list',JSON.stringify(newValue))}}}})
</script>
</body></html>

css文件:

.app-container {padding-bottom: 300px;width: 800px;margin: 0 auto;
}
@media screen and (max-width: 800px) {.app-container {width: 600px;}
}
.app-container .banner-box {border-radius: 20px;overflow: hidden;margin-bottom: 10px;
}
.app-container .banner-box img {width: 100%;
}
.app-container .nav-box {background: #ddedec;height: 60px;border-radius: 10px;padding-left: 20px;display: flex;align-items: center;
}
.app-container .nav-box .my-nav {display: inline-block;background: #5fca71;border-radius: 5px;width: 90px;height: 35px;color: white;text-align: center;line-height: 35px;margin-right: 10px;
}.breadcrumb {font-size: 16px;color: gray;
}
.table {width: 100%;text-align: left;border-radius: 2px 2px 0 0;border-collapse: separate;border-spacing: 0;
}
.th {color: rgba(0, 0, 0, 0.85);font-weight: 500;text-align: left;background: #fafafa;border-bottom: 1px solid #f0f0f0;transition: background 0.3s ease;
}
.th.num-th {flex: 1.5;
}
.th {text-align: center;
}
.th:nth-child(4),
.th:nth-child(5),
.th:nth-child(6),
.th:nth-child(7) {text-align: center;
}
.th.th-pic {flex: 1.3;
}
.th:nth-child(6) {flex: 1.3;
}.th,
.td {position: relative;padding: 16px 16px;overflow-wrap: break-word;flex: 1;
}
.pick-td {font-size: 14px;
}
.main,
.empty {border: 1px solid #f0f0f0;margin-top: 10px;
}
.tr {display: flex;cursor: pointer;border-bottom: 1px solid #ebeef5;
}
.tr.active {background-color: #f5f7fa;
}
.td {display: flex;justify-content: center;align-items: center;
}.table img {width: 100px;height: 100px;
}button {outline: 0;box-shadow: none;color: #fff;background: #d9363e;border-color: #d9363e;color: #fff;background: #d9363e;border-color: #d9363e;line-height: 1.5715;position: relative;display: inline-block;font-weight: 400;white-space: nowrap;text-align: center;background-image: none;border: 1px solid transparent;box-shadow: 0 2px 0 rgb(0 0 0 / 2%);cursor: pointer;transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);-webkit-user-select: none;-moz-user-select: none;-ms-user-select: none;user-select: none;touch-action: manipulation;height: 32px;padding: 4px 15px;font-size: 14px;border-radius: 2px;
}
button.pay {background-color: #3f85ed;margin-left: 20px;
}.bottom {height: 60px;display: flex;align-items: center;justify-content: space-between;padding-right: 20px;border: 1px solid #f0f0f0;border-top: none;padding-left: 20px;
}
.right-box {display: flex;align-items: center;
}
.check-all {cursor: pointer;
}
.price {color: hotpink;font-size: 30px;font-weight: 700;
}
.price-box {display: flex;align-items: center;
}
.empty {padding: 20px;text-align: center;font-size: 30px;color: #909399;
}
.my-input-number {display: flex;
}
.my-input-number button {height: 40px;color: #333;border: 1px solid #dcdfe6;background-color: #f5f7fa;
}
.my-input-number button:disabled {cursor: not-allowed!important;
}
.my-input-number .my-input__inner {height: 40px;width: 50px;padding: 0;border: none;border-top: 1px solid #dcdfe6;border-bottom: 1px solid #dcdfe6;
}
.my-input-number {position: relative;display: inline-block;width: 140px;line-height: 38px;
}
.my-input-number span {-moz-user-select: none;-webkit-user-select: none;-ms-user-select: none;
}
.my-input-number .my-input {display: block;position: relative;font-size: 14px;width: 100%;
}
.my-input-number .my-input__inner {-webkit-appearance: none;background-color: #fff;background-image: none;border-radius: 4px;border: 1px solid #dcdfe6;box-sizing: border-box;color: #606266;display: inline-block;font-size: inherit;height: 40px;line-height: 40px;outline: none;padding: 0 15px;transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);width: 100%;padding-left: 50px;padding-right: 50px;text-align: center;
}
.my-input-number .my-input-number__decrease,
.my-input-number .my-input-number__increase {position: absolute;z-index: 1;top: 1px;width: 40px;height: auto;text-align: center;background: #f5f7fa;color: #606266;cursor: pointer;font-size: 13px;
}
.my-input-number .my-input-number__decrease {left: 1px;border-radius: 4px 0 0 4px;border-right: 1px solid #dcdfe6;
}
.my-input-number .my-input-number__increase {right: 1px;border-radius: 0 4px 4px 0;border-left: 1px solid #dcdfe6;
}
.my-input-number .my-input-number__decrease.is-disabled,
.my-input-number .my-input-number__increase.is-disabled {color: #c0c4cc;cursor: not-allowed;
}

实现思路:

1.基本渲染: v-for遍历、:class动态绑定样式

2.删除功能 : v-on 绑定事件,获取当前行的id

3.修改个数 : v-on绑定事件,获取当前行的id,进行筛选出对应的项然后增加或减少

4.全选反选

  1. 必须所有的小选框都选中,全选按钮才选中 → every

  2. 如果全选按钮选中,则所有小选框都选中

  3. 如果全选取消,则所有小选框都取消选中

声明计算属性,判断数组中的每一个checked属性的值,看是否需要全部选

5.统计 选中的 总价 和 总数量 :通过计算属性来计算选中的总价和总数量

6.持久化到本地: 在数据变化时都要更新下本地存储 watch。

测试结果:

这篇文章就到这里了,下次见!

🥇原创不易,还希望各位大佬支持一下!

👍点赞,你的认可是我创作的动力 !

🌟收藏,你的青睐是我努力的方向!

✏️评论,你的意见是我进步的财富!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/143979.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

计算机网络32——Linux-文件io-2文件系统

1、阻塞和非阻塞 想要将文件以非阻塞方式打开&#xff0c;有两种方式 &#xff08;1&#xff09;需要将文件关闭&#xff0c;再用非阻塞方式打开 &#xff08;2&#xff09;fctnl函数&#xff0c;先获取旧属性&#xff0c;再添加一个新属性 阻塞函数 阻塞函数一直在等待输入…

MATLAB系列09:图形句柄

MATLAB系列09&#xff1a;图形句柄 9. 图形句柄9.1 MATLAB图形系统9.2 对象句柄9.3 对象属性的检测和更改9.3.1 在创建对象时改变对象的属性9.3.2 对象创建后改变对象的属性 9.4 用 set 函数列出可能属性值9.5 自定义数据9.6 对象查找9.7 用鼠标选择对象9.8 位置和单位9.8.1 图…

使用微信小程序唤起导航的常用方式

1.微信内置地图 可以使用小程序的wx.openLocation方法&#xff0c;该方法可以打开微信内置地图&#xff0c;并显示指定的位置坐标。如果用户手机上安装了其他地图应用&#xff0c;可能会出现选择使用哪个地图应用进行导航的提示。 wx.openLocation({latitude: 目标地点纬度,lo…

滑动窗口(7)_串联所有单词的字串

目录 1. 题目链接: 2. 题目描述 : 3. 解法 : 题目解析: 算法思路 : 图解流程: 代码展示 : 结果分析 : 1. 题目链接: OJ链接:串联所有单词的字串 2. 题目描述 : 给定一个字符串 s 和一个字符串数组 words。 words 中所有字符串 长度相同。 s 中的 串联子串 是指一个包含 w…

Llama 3.1 Omni:颠覆性的文本与语音双输出模型

你可能听说过不少关于语言模型的进展,但如果告诉你,有一种模型不仅能生成文本,还能同时生成语音,你会不会觉得特别酷?今天咱们就来聊聊一个相当前沿的项目——Llama 3.1 Omni模型。这个模型打破了传统的文字生成边界,直接让文本和语音同时输出,实现了真正的"多模态…

【uni-app】小兔鲜项目-基础架构-请求和上传文件拦截器

注意事项 uni.request 请求封装 请求和上传文件拦截器 uniapp 拦截器&#xff1a; uni.addInterceptor 接口说明&#xff1a;接口文档 实现需求 拼接基础地址设置超时时间添加请求头标识添加 token 参考代码 // src/utils/http.ts// 请求基地址 const baseURL https://pca…

fastadmin 部署后前台会员中心出现404错误

访问前台会员中心出现404错误。 解决&#xff1a;nginx访问站点增加伪静态 location / {if (!-e $request_filename){rewrite ^(.*)$ /index.php?s$1 last; break;} }在phpstydy中增加伪静态&#xff0c;如图&#xff1a;

Renesas R7FA8D1BH (Cortex®-M85)的UART使用介绍

目录 概述 1 软硬件 1.1 软硬件环境信息 1.2 开发板信息 1.3 调试器信息 2 FSP配置UART 2.1 配置参数 2.2 UART模块介绍 3 接口函数介绍 3.1 R_SCI_B_UART_Open() 3.2 R_SCI_B_UART_Close() 3.3 R_SCI_B_UART_Read() 3.4 R_SCI_B_UART_Write() 3.5 R_SCI_B_UAR…

Tiny-universe-taks1-LLama3模型原理

LLama3模型原理-学习打卡 大模型相关知识笔记transformersSelf-Attention(自注意力机制)Multi-Head-Attention&#xff08;多头注意力机制&#xff09; LLama梳理 大模型相关知识笔记 transformers 目前市面上主流的大模型算法都给予Transformers架构&#xff0c;如下图所示&…

Spring WebFlux实践与源码解析

Spring WebFlux和Spring MVC区别 如下来自官网Spring MVC和Spring WebFlux技术栈的区别。 可以看到Spring MVC主要是基于Servlet API使用同步阻塞IO架构&#xff0c;每来一个请求都需要启动一个线程进行处理。这种架构对于大量I/O密集型的请求&#xff0c;需要同时启动大量的线…

CTFshow——萌新密码1-4

萌新密码1 53316C6B5A6A42684D3256695A44566A4E47526A4D5459774C5556375A6D49324D32566C4D4449354F4749345A6A526B4F48303D 首先&#xff0c;16进制转字符串 在线字符串/十六进制互相转换—LZL在线工具 (lzltool.cn) S1lkZjBhM2ViZDVjNGRjMTYwLUV7ZmI2M2VlMDI5OGI4ZjRkOH0 发现…

ConflictingBeanDefinitionException | 运行SpringBoot项目时报错bean定义冲突解决方案

具体报错&#xff1a; Caused by: org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name ‘CommissionMapperImpl’ for bean class [com.xxx.mapper.carrier.CommissionMapperImpl] conflicts with existing, non-co…

OpenAI GPT o1技术报告阅读(2)- 关于模型安全性的测试案例

✨报告阅读&#xff1a;使用大模型来学习推理(Reason) 首先是原文链接&#xff1a;https://openai.com/index/learning-to-reason-with-llms/ 接下来我们看一个简单的关于模型安全性的测试&#xff0c;当模型被问到一个有风险的话题时&#xff0c;会如何思考并回答用户呢&…

JAVA基础,利用for循环找水仙花个数

public class learn2 {public static void main(String[] args) {int count 0;//定义水仙花的个数for (int i 100; i<999; i){int g i%10;int s i/10%10;int b i/100%10;if (i b*b*b s*s*s g*g*g){count1;System.out.println(i);}}System.out.println("一共有"…

MySQL 中的索引覆盖扫描:加速查询的秘密武器

在 MySQL 数据库的使用中&#xff0c;索引是提高查询性能的重要工具。而索引覆盖扫描&#xff08;Index Covering Scan&#xff09;更是一种能显著提升查询效率的技术。本篇文章我们就来深入了解一下 MySQL 中的索引覆盖扫描是什么。 一、什么是索引覆盖扫描 在 MySQL 中&…

攻防世界——simple_php(NO.GFSJ0485)

目录 基础环境解题过程 基础环境 靶机&#xff1a;xctf 方向&#xff1a;Web 难度&#xff1a;1 解题过程 打开题目 我们可以看到该题目的源码&#xff0c;大概分析一下&#xff1a; 我们需要用GET方式传两个值&#xff0c;分别为a,b。 接下来是第一个if&#xff0c;这里判…

分享几种方式获取免费精致的Live2d模型

文章目录 1、 Live2D官方示例数据集&#xff08;可免费下载&#xff09;2、模之屋3、unity商店4、直接b站搜索5、youtube6、BOOTH完结 1、 Live2D官方示例数据集&#xff08;可免费下载&#xff09; 官方提供了一些 Live2D实例模型给大家下载使用 地址&#xff1a;https://ww…

栈、队列、链表

基于《啊哈&#xff01;算法》和《数据结构》&#xff08;人民邮电出版社&#xff09; 本博客篇幅较多&#xff0c;读者根据目录选择&#xff0c;不理解的可留言和私信。 栈、队列、链表都是线性结构。 三者都不是结构体、数组这种数据类型&#xff0c;我认为更像是一种算法…

基于单片机的远程无线控制系统设计

摘 要 &#xff1a; 主要介绍了一种以单片机 AT89C2051 、 无线模块 APC200A-43 和继电器为核心的远程智能控制系统。 该系统通过对单片机功能的研究 &#xff0c; 使用单片机的输入输出口和中断实现对控制信号的处理&#xff0c; 通过调试无线通讯模块 &#xff0c; 控制…

Mybatis 快速入门(maven)

文章目录 需求建表新建了数据库但是navicat界面没有显示 新建maven项目 注意导入依赖 总结 黑马学习笔记 需求 建表 注意&#xff1a;设置字符集 减少出错 drop database mybatis; create database mybatis charset utf8; use mybatis;drop table if exists tb_user;create…