Vue2二、指令补充,computed 计算属性vs方法,watch 侦听器,
一、指令补充
1.修饰符。2.动态操作class。3.动态操作style。4.v-model 用于其他表单元素
1.修饰符
① 按键修饰符
@keyup.enter → 键盘回车监听
<body><div id="app"><h3>@keyup.enter → 监听键盘回车事件</h3><input v-model="username" type="text" @keyup.enter="fn"></div><script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script><script>const app = new Vue({el: '#app',data: {username: ''},methods: {fn() {//// if (e.key === 'Enter') {// console.log('按下回车了')// }console.log('Vue');}}})</script>
</body>
② v-model修饰符
v-model.trim → 去除首尾空格 。 v-model.number → 转数字
③ 事件修饰符
@事件名 . stop → 阻止冒泡 。@事件名 . prevent → 阻止默认行为
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><style>.father {width: 200px;height: 200px;background-color: pink;margin-top: 20px;}.son {width: 100px;height: 100px;background-color: skyblue;}</style>
</head><body><div id="app"><h3>v-model修饰符 .trim .number</h3><!-- v-model作用: 给 表单元素 使用, 双向数据绑定 → 可以快速 获取 或 设置 表单元素内容 --><!--.trim去首尾空格 -->姓名:<input v-model.trim="username" type="text"><br><!-- .number转数字类型 -->年纪:<input v-model.number="age" type="text"><br><h3>@事件名.stop → 阻止冒泡</h3><div @click="fatherFn" class="father"><!-- .stop阻止冒泡 --><div @click.stop="sonFn" class="son">儿子</div></div><h3>@事件名.prevent → 阻止默认行为</h3><!-- .prevent阻止默认行为-超链接的跳转 --><a @click.prevent 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>
</body></html>
2.动态操作class
语法:class="对象/数组"
① 对象 →键就是类名,值是布尔值。如果值为 true,有这个类,否则没有这个类
适用场景:一个类名,来回切换
<div class="box":class="{类名1:布尔值,类名2:布尔值}"></div>
② 数组→数组中所有的类,都会添加到盒子上,本质就是一个 class 列表
适用场景:批量添加或删除类
<div class="box" :class="[ 类名1, 类名2,类名3 ]"></div>
//
:class="['pink', 'big']"
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><style>.box {width: 200px;height: 200px;border: 3px solid #000;font-size: 30px;margin-top: 10px;}.pink {background-color: pink;}.big {width: 300px;height: 300px;}</style>
</head><body><!-- v-bind 对于样式控制的增强 - 操作class --><!-- 语法:class="对象/数组" --><!-- ①对象→键就是类名,值是布尔值。如果值为true,有这个类,否则没有这个类,适用场景:一个类名,来回切换 --><!-- ②数组→数组中所有的类,都会添加到盒子上,本质就是一个class 列表,适用场景:批量添加或删除类--><div id="app"><div class="box" :class="{pink:flag1,big:flag2}">程序员</div><div class="box" :class="['pink','big']">程序员</div></div><script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script><script>const app = new Vue({el: '#app',data: {flag1: false,flag2: true}})</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"><title>Document</title><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>
</head><body><div id="app"><ul><li v-for="(item,index) in list" :key="item.id" @click="num=index"><a :class="{active:num === index}" href="#">{{item.name}}</a></li></ul></div><script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script><script>const app = new Vue({el: '#app',data: {num: 2,list: [{id: 1,name: '京东秒杀'}, {id: 2,name: '每日特价'}, {id: 3,name: '品类秒杀'}]}})</script>
</body>
</html>
3.动态操作style
语法:style ="样式对象"
适用场景:某个具体属性的动态设置
<div class="box":style="{CSS属性名1:CSS属性值,CSS属性名2:CSS属性值 }"></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"><title>Document</title><style>.box {width: 200px;height: 200px;background-color: rgb(187, 150, 156);}</style>
</head><body><div id="app"><!-- 带中横线的css属性要变小驼峰 --><div class="box" :style="{width:num1, height:num2+'px', backgroundColor:mycolor}"></div></div><script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script><script>const app = new Vue({el: '#app',data: {num1: '300px',mun2: 300,mycolor: "green"}})</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"><title>Document</title><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>
</head>
<body><div id="app"><div class="progress"><div class="inner" :style="{width:num+'%' }"><span>{{num}}%</span></div></div><button @click="num=25">设置25%</button><button @click="num=50">设置50%</button><button @click="num=75">设置75%</button><button @click="num=100">设置100%</button></div><script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script><script>const app = new Vue({el: '#app',data: {num: 10}})</script>
</body>
</html>
4.v-model 用于其他表单元素
输入框 input:text | value |
文本域 textarea | checked |
复选框 input:checkbox | value |
单选框 input:radio | value |
下拉菜单 select | checked |
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><style>textarea {display: block;width: 240px;height: 100px;margin: 10px 0;}</style>
</head><body><div id="app"><h3>小黑学习网</h3>姓名:<input type="text"><br><br> 是否单身:<input type="checkbox" v-model="isSLingle"><br><br><!-- 前置理解:1. name: 给单选框加上 name 属性 可以分组 → 同一组互相会互斥2. value: 给单选框加上 value 属性,用于提交给后台的数据结合 Vue 使用 → v-model-->性别:<!-- -model绑定了数据→找到对应value值的标签添加checked属性 --><input type="radio" name="gender" v-model="gender" value="0">男<input type="radio" name="gender" v-model="gender" value="1">女<br><br><!-- 前置理解:1. option 需要设置 value 值,提交给后台2. select 的 value 值,关联了选中的 option 的 value 值结合 Vue 使用 → v-model-->所在城市:<select v-model="selVal"><option value="bj" >北京</option><option value="sh" >上海</option><option value="cd" >成都</option><option value="nj" >南京</option></select><br><br> 自我描述:<textarea></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: '',isSLingle: true,gender: 0,selVal: 'nj'}})</script>
</body></html>
二、computed 计算属性
概念:基于现有的数据,计算出来的新属性。 依赖的数据变化,自动重新计算。
计算属性默认的简写,只能读取访问, 不能 "修改" 。
如果要 "修改" → 需要写计算属性的 完整写法 。
例1小黑的礼物清单
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><style>table {border: 1px solid #000;text-align: center;width: 240px;}th,td {border: 1px solid #000;}h3 {position: relative;}</style>
</head><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>礼物总数:{{totalNum}} 个</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: {totalNum() {// return累加的结果// return xx(前一次累加的结果,这一次要累加的结果)=>{}return this.list.reduce((sum, item) => {return sum += item.num}, 0)}}})</script>
</body></html>
例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"><title>Document</title>
</head><body><div id="app">姓:<input type="text" v-model="name1"><br> 名: <input type="text" v-model="name2"><br><p>姓名:{{nameFn}}</p><button @click='clickfn()'>修改姓名</button></div><script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script><script>const app = new Vue({el: '#app',data: {name1: '',name2: '',},computed: {nameFn: {//读取-依赖别的数据计算结果get() {return this.name1 + this.name2},// 修改-如果真的修改了计算属性的结果//set这里的形参能自动接收到修改后的结果set(value) {// 两个输入框的值修改-小明//[0,1]this.name1 = value.substring(0, 1)this.name2 = value.substring(1)}}},methods: {clickfn() {// 修改计算属性得到的名字this.nameFn = '小明'}}})</script>
</body></html>
1.计算属性 vs 方法
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><style>table {border: 1px solid #000;text-align: center;width: 300px;}th,td {border: 1px solid #000;}h3 {position: relative;}span {position: absolute;left: 145px;top: -4px;width: 16px;height: 16px;color: white;font-size: 12px;text-align: center;border-radius: 50%;background-color: #e63f32;}</style>
</head><body><div id="app"><!-- <h3>小黑的礼物清单🛒<span>{{totalCount}}</span></h3> --><h3>小黑的礼物清单🛒<span>{{fn()}}</span></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>礼物总数:{{ totalCount }} 个</p> --><p>礼物总数:{{fn() }} 个</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: 3}, {id: 2,name: '玩具',num: 2}, {id: 3,name: '铅笔',num: 5}, ]},// computed: {// totalCount() {// console.log('我是计算属性');// let total = this.list.reduce((sum, item) => sum + item.num, 0)// return total// }// },methods: {fn() {console.log('我是普通函数');let total = this.list.reduce((sum, item) => sum + item.num, 0)return total}}})</script>
</body></html>
2.例:综合案例 - 成绩案例
需求说明:
1. 渲染功能: v-if v-else v-for v-bind:class
2. 删除功能:点击传参 filter过滤覆盖原数组。 .prevent 阻止默认行为
3. 添加功能:v-model v-model修饰符(.trim .number)。 unshift 修改数组更新视图
4. 统计总分,求平均分: 计算属性 reduce求和
<!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="./styles/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 v-if="list.length"><tr v-for="(item,index) in list" :key="item.id"><td>{{index+1}}</td><td>{{item.subject}}</td><!-- v-bind 动态的设置html的标签属性 → src url title... --><!-- 2语法: v-bind:属性名="表达式"3. 注意: 简写形式 :属性名="表达式" --><td :class="{red:item.score<60}">{{item.score}}</td><td><a @click="del(item.id)" href="#">删除</a></td></tr></tbody><tbody v-else><tr><td colspan="5"><span class="none">暂无数据</span></td></tr></tbody><tfoot><tr><td colspan="5"><span>总分:{{ totalScore}}</span><span style="margin-left: 50px">平均分:{{avgScore}}</span></td></tr></tfoot></table></div><div class="form"><div class="form-item"><div class="label">科目:</div><div class="input"><input type="text" placeholder="请输入科目" v-model="subject" /></div></div><div class="form-item"><div class="label">分数:</div><div class="input"><input type="text" placeholder="请输入分数" v-model.number="score" /></div></div><div class="form-item"><div class="label"></div><div class="input"><button class="submit" @click="add()">添加</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: {//删除del(ids) {this.list = this.list.filter(item => item.id != ids)},//添加add() {if (this.subject.trim() === '' || this.score === '') {alert('数据不能为空')return}this.list.unshift({id: +new Date(),subject: this.subject,score: this.score})this.subject = ''this.score = ''}},computed: {// 总分totalScore() {return this.list.reduce((sum, item) => sum + item.score, 0)},//平均分avgScore() {return this.totalScore / this.list.length}}})</script>
</body></html>
三、watch 侦听器
作用: 监视数据变化 ,执行一些 业务逻辑 或 异步操作。
① 简单写法
监视简单类型的变化
watch: {
数据属性名 (newValue, oldValue) {
一些业务逻辑 或 异步操作。
},
'对象.属性名' (newValue, oldValue) {
一些业务逻辑 或 异步操作。
}
}
② 完整写法
添加额外的配置项 (深度监视复杂类型,立刻执行)
watch: {// watch 完整写法
数据属性名: {
deep: true, // 深度监视(针对复杂类型)
immediate: true, // 是否立刻执行一次handler
handler (newValue) {
console.log(newValue)
}
}
}
例:翻译语言
需求:输入内容, 修改语言 ,都实时翻译
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title><style>* {margin: 0;padding: 0;box-sizing: border-box;font-size: 18px;}#app {padding: 10px 20px;}.query {margin: 10px 0;}.box {display: flex;}textarea {width: 300px;height: 160px;font-size: 18px;border: 1px solid #dedede;outline: none;resize: none;padding: 10px;}textarea:hover {border: 1px solid #1589f5;}.transbox {width: 300px;height: 160px;background-color: #f0f0f0;padding: 10px;border: none;}.tip-box {width: 300px;height: 25px;line-height: 25px;display: flex;}.tip-box span {flex: 1;text-align: center;}.query span {font-size: 18px;}.input-wrap {position: relative;}.input-wrap span {position: absolute;right: 15px;bottom: 15px;font-size: 12px;}.input-wrap i {font-size: 20px;font-style: normal;}</style>
</head><body><div id="app"><!-- 条件选择框 --><div class="query"><span>翻译成的语言:</span><select v-model="obj.lang"><option value="italy">意大利</option><option value="english">英语</option><option value="german">德语</option></select></div><!-- 翻译框 --><div class="box"><div class="input-wrap"><textarea v-model="obj.words"></textarea><span><i>⌨️</i>文档翻译</span></div><div class="output-wrap"><div class="transbox">{{txt}}</div></div></div></div><script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script><script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script><script>// 接口地址:https://applet-base-api-t.itheima.net/api/translate// 请求方式:get// 请求参数:// (1)words:需要被翻译的文本(必传)// (2)lang: 需要被翻译成的语言(可选)默认值-意大利// -----------------------------------------------const app = new Vue({el: '#app',data: {words: '',txt: '',obj: {words: '',// timer: ''lang: 'italy'}},// 具体讲解:(1) watch语法 (2) 具体业务实现watch: {// words(新值,老值){// words(newval, oldval) {// console.log(`数据变化了`);// console.log(newval, oldval)// }// -------------------------------------------------------------------obj: {// 复杂类型深度监听·→里面的属性变化也能监听到deep: true,// 打开页面,默认调用一次handler函数// immediate: true,// 监听到数据变化执行的代码·-·handlerhandler(newval) {// console.log(`obj变化了`, newval);clearTimeout(this.timer) //this.timer→timer 也是挂载到了Vue实例上,只不过是普通变量,不是响应式数据this.timer = setTimeout(async() => {const res = await axios({url: 'https://applet-base-api-t.itheima.net/api/translate',params: {words: this.obj.words,lang: this.obj.lang}})// this.txt = res.data.data}, 500)}}// ---------------------------------------------------------------------// 属性名要加引号// 'obj.words' (newval) {// // console.log(`name变化了`)// console.log(newval)// clearTimeout(this.timer) //this.timer→timer 也是挂载到了Vue实例上,只不过是普通变量,不是响应式数据// this.timer = setTimeout(async() => {// const res = await axios({// url: 'https://applet-base-api-t.itheima.net/api/translate',// params: {// words: this.obj.words// }// })// // // this.txt = res.data.data// }, 500)// }}})</script>
</body></html>
综合案例 - 水果购物车
需求说明:
1. 渲染功能: v-if/v-else v-for :class
2. 删除功能: 点击传参 filter 过滤覆盖原数组
3. 修改个数: 点击传参 find 找对象
4. 全选反选:计算属性 computed 完整写法 get/set
5. 统计 选中的 总价和总数量: 计算属性 computed reduce 条件求和
6. 持久化到本地: watch 监视, localStorage , JSON.stringify , JSON.parse
<!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 class="main" v-if="fruitList.length>0"><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 :class="{active: item.isChecked}" class="tr" v-for="(item,index) in fruitList" :key="item.id"><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 class="decrease" @click="item.num--" :disabled="item.num<1"> - </button><span class="my-input__inner">{{ item.num }}</span><button class="increase" @click="item.num++"> + </button></div></div><div class="td">{{ item.price * item.num }}</div><div class="td"><button @click="del(item.id)">删除</button></div></div></div></div><!-- 底部 --><div class="bottom"><!-- 全选 --><label class="check-all"><!-- 点击的时候 checked值发生变化, isAll的值变化 --><input type="checkbox" v-model="isAll" />全选</label><div class="right-box"><!-- 所有商品总价 --><span class="price-box">总价 : ¥ <span class="price">{{totalPrice}}</span></span><!-- 结算按钮 --><button class="pay">结算( {{totalNum}} )</button></div></div></div><!-- 空车 --><div class="empty" v-else>🛒空空如也</div></div><script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script><script>const app = new Vue({el: '#app',data: {// 水果列表fruitList: JSON.parse(localStorage.getItem('fruit')) || [{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,},],},methods: {del(id) {this.fruitList = this.fruitList.filter(item=>item.id!=id)}},computed: {totalPrice() {return this.fruitList.reduce((sum, item)=>{if (item.isChecked) {return sum + item.num * item.price} else {return sum}}, 0)},totalNum () {// 勾选的才累加return this.fruitList.reduce((sum, item)=>{if (item.isChecked) {return sum + item.num} else {return sum}}, 0)},// 全选的值 = 上面每个产品的isChecked; 点击全选,修改上面的每个产品的isChecked → 可以获取可以修改isAll: {get() {// 如果上面的每个isChecked都是true,全选就是true// return this.fruitList.every(item=>item.isChecked===true)return this.fruitList.every(item=>item.isChecked)},// val 来自于 v-modelset(val) {// console.log(val)this.fruitList.forEach(item=>item.isChecked = val)}}},watch: {fruitList: {deep:true,handler (newval) {localStorage.setItem('fruit', JSON.stringify(newval))}}}})</script></body>
</html>